ReachBellDocs

Campaigns API

A campaign is a single broadcast push sent to a segment of your audience. The Campaigns API mirrors what the dashboard's campaign builder does — create, schedule, send, edit, delete, watch live progress, and export results.

All routes require authentication. Server-side callers should use x-api-key; dashboard sessions use JWT + x-org-id. See Authentication.

Campaign body shape

Every create and update request uses the same body shape:

{
  "name":      "Spring sale launch",
  "projectId": "proj_01HX2...",
  "content": {
    "title":    "Spring sale is live",
    "body":     "30% off everything until Sunday.",
    "iconUrl":  "https://cdn.example.com/icon-192.png",
    "imageUrl": "https://cdn.example.com/hero.jpg",
    "clickUrl": "https://example.com/sale",
    "actions": [
      { "action": "shop", "title": "Shop now" }
    ]
  },
  "segment": {
    "filters": { "country": ["US", "CA"], "tags": ["newsletter"] }
  },
  "schedule": {
    "mode": "scheduled",
    "sendAt": "2026-06-15T14:00:00.000Z",
    "timezone": "America/New_York"
  },
  "utm": {
    "source":   "reachbell",
    "medium":   "push",
    "campaign": "spring-sale"
  }
}
  • schedule.mode is "immediate", "scheduled", or "draft".
  • segment is either inline filters (above) or { "savedSegmentId": "seg_..." }.
  • utm is optional. If present, ReachBell appends the params to content.clickUrl and to every action URL.

Create and send

POST /campaigns

Create a campaign. If schedule.mode is "immediate", ReachBell starts sending right away. "scheduled" queues it for sendAt. "draft" saves without dispatch.

curl -X POST https://api.reachbell.com/campaigns \
  -H "x-api-key: rb_live_yourkey" \
  -H "Content-Type: application/json" \
  -d '{
    "name":      "Spring sale launch",
    "projectId": "proj_01HX2...",
    "content": {
      "title":    "Spring sale is live",
      "body":     "30% off everything until Sunday.",
      "iconUrl":  "https://cdn.example.com/icon-192.png",
      "clickUrl": "https://example.com/sale"
    },
    "segment":  { "filters": { "country": ["US"] } },
    "schedule": { "mode": "immediate" },
    "utm":      { "source": "reachbell", "medium": "push", "campaign": "spring-sale" }
  }'

Success:

{
  "id":        "cmp_01HX5...",
  "name":      "Spring sale launch",
  "status":    "sending",
  "stats":     { "queued": 14823, "sent": 0, "clicked": 0, "failed": 0 },
  "createdAt": "2026-06-12T09:00:00.000Z"
}

List campaigns

GET /campaigns/project/:projectId

Paginated. Filters: status, from, to, q (name search).

curl "https://api.reachbell.com/campaigns/project/proj_01HX2...?status=sent&pageSize=20" \
  -H "x-api-key: rb_live_yourkey"

Get campaign detail with stats

GET /campaigns/:id

Returns the campaign object including the full stats block: queued, sent, delivered, clicked, dismissed, failed, ctr, deliveryRate.

curl https://api.reachbell.com/campaigns/cmp_01HX5... \
  -H "x-api-key: rb_live_yourkey"

Update a draft

PATCH /campaigns/:id

Only campaigns with status: "draft" can be edited. Once a campaign starts sending the API rejects the patch with 409 Conflict.

curl -X PATCH https://api.reachbell.com/campaigns/cmp_01HX5... \
  -H "x-api-key: rb_live_yourkey" \
  -H "Content-Type: application/json" \
  -d '{ "content": { "title": "Spring sale — last chance" } }'

Delete

DELETE /campaigns/:id

Deletes a draft, scheduled, or cancelled campaign. Sent campaigns are archived rather than destroyed — the delete returns 409 and you should archive from the dashboard instead.

curl -X DELETE https://api.reachbell.com/campaigns/cmp_01HX5... \
  -H "x-api-key: rb_live_yourkey"

Re-send

POST /campaigns/:id/send

Re-dispatch a sent or cancelled campaign. The campaign content and segment are reused; you can pass an override segment in the body.

curl -X POST https://api.reachbell.com/campaigns/cmp_01HX5.../send \
  -H "x-api-key: rb_live_yourkey" \
  -H "Content-Type: application/json" \
  -d '{ "segment": { "filters": { "tags": ["did-not-open"] } } }'

Re-sends count as a fresh campaign in your stats. The original campaign's numbers are not mutated.

Live progress over SSE

GET /campaigns/:id/stream?token=<JWT>&orgId=<orgId>

Server-sent events stream of delivery progress. The dashboard uses this to power the live progress bar on the campaign detail page. Each event is a JSON object with the current stats snapshot.

curl "https://api.reachbell.com/campaigns/cmp_01HX5.../stream?token=eyJ...&orgId=org_01HX1..." \
  -H "Accept: text/event-stream"

Sample event payload:

{
  "queued":  14823,
  "sent":    9211,
  "clicked": 412,
  "failed":  18,
  "status":  "sending"
}

The SSE endpoint is JWT-only (query params, since browsers can't set headers on EventSource). API key callers should poll GET /campaigns/:id instead.

Export results to CSV

GET /campaigns/export/project/:projectId.csv

Streams a CSV of every campaign on the project with stats columns. Useful for spreadsheet analysis or warehouse imports.

curl "https://api.reachbell.com/campaigns/export/project/proj_01HX2....csv?from=2026-01-01&to=2026-06-30" \
  -H "x-api-key: rb_live_yourkey" \
  -o campaigns.csv

What's next?

  • Run an A/B test inside a campaign to find the highest-CTR variant.
  • Build a reusable segment to target campaigns precisely.
  • Attribute every click with UTM tracking.