Skip to main content

Campaigns API

A campaign is a first-class send container: it holds an audience, one or more ordered messages, and a lifecycle. Single-message campaigns cover one-off sends; multi-message campaigns let you author a sequence of messages, release them together, and read a per-message performance report.

These endpoints are authenticated with a bearer token:

Authorization: Bearer YOUR_ACCESS_TOKEN

The campaign object

FieldTypeDescription
idstringCampaign UUID.
namestringDisplay name.
statusstringOne of draft, scheduled, live, completed, paused, archived.
categorystringMessaging category: transactional or marketing. Defaults to marketing.
targetobjectAudience target. Defaults to {"type": "all"}.
contentobjectInline notification content. Omitted when template_id supplies the content.
template_idstringTemplate UUID, when content comes from a template.
channel_idsarray of stringsChannel UUIDs the campaign sends through.
holdout_percentageintegerPercentage of the audience held out as a control group.
scheduled_atstringRFC 3339 send time. Omitted for immediate sends.
messagesarrayThe campaign's ordered messages (see below). The top-level content / template_id / channel_ids / scheduled_at mirror the first message for single-message consumers.
created_atstringCreation timestamp.
updated_atstringLast-update timestamp.

Each entry in messages:

FieldTypeDescription
idstringMessage UUID.
contentobjectInline notification content. Omitted when template_id is set.
template_idstringTemplate UUID.
channel_idsarray of stringsChannel UUIDs for this message.
scheduled_atstringRFC 3339 send time for this message. Omitted = sends with the campaign.
positionintegerZero-based order within the campaign.
job_idstringThe notification job this message produced on release. Empty until released.

POST /api/v1/campaigns

Creates a campaign as a draft. Supply either messages (a multi-message campaign) or the top-level content + channel_ids (a single message). Send the draft later with POST /api/v1/campaigns/:id/release.

Request Body

{
"name": "Spring onboarding",
"category": "marketing",
"target": { "type": "all" },
"holdout_percentage": 5,
"messages": [
{
"content": { "title": "Welcome!", "body": "Thanks for joining." },
"channel_ids": ["ch_11111111-2222-3333-4444-555555555555"]
},
{
"template_id": "tpl_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"channel_ids": ["ch_11111111-2222-3333-4444-555555555555"],
"scheduled_at": "2026-05-03T09:00:00Z"
}
]
}
FieldTypeRequiredDescription
namestringYesCampaign name.
categorystringNotransactional or marketing. Defaults to marketing.
targetobjectNoAudience target. Defaults to {"type": "all"}.
contentobjectConditionalInline content for a single-message campaign. Required (with channel_ids) when messages is absent.
template_idstringNoTemplate UUID supplying the content. Must belong to your tenant.
channel_idsarray of stringsConditionalChannel UUIDs. Required with content when messages is absent.
holdout_percentageintegerNoPercentage of the audience to hold out.
scheduled_atstringNoRFC 3339 send time.
messagesarrayConditionalOrdered message list for a multi-message campaign. Each message takes content, template_id, channel_ids, and an optional per-message scheduled_at.

Response — 201 Created

{
"id": "cmp_9f8e7d6c-5b4a-3210-fedc-ba9876543210",
"name": "Spring onboarding",
"status": "draft",
"category": "marketing",
"target": { "type": "all" },
"content": { "title": "Welcome!", "body": "Thanks for joining." },
"channel_ids": ["ch_11111111-2222-3333-4444-555555555555"],
"holdout_percentage": 5,
"messages": [
{
"id": "msg_00000000-0000-0000-0000-000000000001",
"content": { "title": "Welcome!", "body": "Thanks for joining." },
"channel_ids": ["ch_11111111-2222-3333-4444-555555555555"],
"position": 0
},
{
"id": "msg_00000000-0000-0000-0000-000000000002",
"template_id": "tpl_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"channel_ids": ["ch_11111111-2222-3333-4444-555555555555"],
"scheduled_at": "2026-05-03T09:00:00Z",
"position": 1
}
],
"created_at": "2026-05-01T12:00:00Z",
"updated_at": "2026-05-01T12:00:00Z"
}

Error Codes

StatusReason
401Missing or invalid credentials.
422name missing, neither messages nor content + channel_ids supplied, invalid scheduled_at, unknown category, or template_id does not belong to your tenant.

GET /api/v1/campaigns

Returns a paginated list of campaigns for the authenticated tenant.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number (1-based).
page_sizeinteger20Number of results per page.
statusstringOptional. Filter by lifecycle status (e.g. draft, live).

Response — 200 OK

{
"items": [
{
"id": "cmp_9f8e7d6c-5b4a-3210-fedc-ba9876543210",
"name": "Spring onboarding",
"status": "live",
"category": "marketing",
"target": { "type": "all" },
"channel_ids": ["ch_11111111-2222-3333-4444-555555555555"],
"holdout_percentage": 5,
"messages": [],
"created_at": "2026-05-01T12:00:00Z",
"updated_at": "2026-05-02T09:00:00Z"
}
],
"total": 12,
"page": 1,
"page_size": 20
}

GET /api/v1/campaigns/:id

Retrieves a single campaign, including its full messages list.

Response — 200 OK

Returns the full campaign object (same shape as the create response).

Error Codes

StatusReason
401Missing or invalid credentials.
404No campaign found with that ID, or it belongs to a different tenant.

PUT /api/v1/campaigns/:id

Replaces a draft campaign's audience and messages. The entire message list is replaced, not merged. Campaigns that have left the draft state cannot be edited.

Request Body

{
"name": "Spring onboarding v2",
"category": "marketing",
"target": { "type": "all" },
"holdout_percentage": 10,
"messages": [
{
"content": { "title": "Welcome aboard", "body": "Glad you are here." },
"channel_ids": ["ch_11111111-2222-3333-4444-555555555555"]
}
]
}
FieldTypeRequiredDescription
namestringYesCampaign name.
categorystringNotransactional or marketing.
targetobjectNoReplacement audience target.
holdout_percentageintegerNoReplacement holdout percentage.
messagesarrayYesReplacement message list. At least one message is required.

Response — 200 OK

Returns the full updated campaign object.

Error Codes

StatusReason
401Missing or invalid credentials.
404Campaign not found.
409The campaign is no longer a draft.
422Empty messages, or a message has an invalid scheduled_at.

PATCH /api/v1/campaigns/:id

Applies a lifecycle action to the campaign. Pausing or archiving also deactivates any recurring schedule owned by the campaign; resuming reactivates it.

Request Body

{ "action": "pause" }
ActionEffectAllowed from
pauseStops sending; status becomes paused.scheduled, live
resumeResumes sending; status becomes scheduled.paused
archiveRetires the campaign; status becomes archived.Any non-archived status
unarchiveRestores the exact pre-archive status.archived

Response — 200 OK

Returns the full updated campaign object.

Error Codes

StatusReason
401Missing or invalid credentials.
404Campaign not found.
409The action is not allowed from the campaign's current status.
422Unknown action.

POST /api/v1/campaigns/:id/release

Sends a draft campaign: one notification job is created per message, each scheduled at its message's own scheduled_at (or immediately when absent). The campaign transitions out of draft and each message's job_id is populated.

Release is idempotent: a message already linked to a job is skipped, so retrying after a partial failure resumes where it left off.

Response — 200 OK

Returns the full updated campaign object, with status advanced and job_id set on each released message.

Error Codes

StatusReason
401Missing or invalid credentials.
404Campaign not found.
409The campaign is not a draft (already released).

GET /api/v1/campaigns/:id/report

Returns the campaign's funnel KPIs plus per-channel and per-message breakdowns over a time window.

Query Parameters

ParameterTypeDefaultDescription
startstring30 days agoRFC 3339 start of the reporting window.
endstringnowRFC 3339 end of the reporting window.

Response — 200 OK

{
"sent": 4200,
"delivered": 4012,
"opened": 1830,
"clicked": 402,
"converted": 96,
"revenue_cents": 481500,
"by_channel": [
{ "channel": "ios", "delivered": 2400, "opened": 1100, "clicked": 240 },
{ "channel": "android", "delivered": 1612, "opened": 730, "clicked": 162 }
],
"by_message": [
{ "position": 0, "label": "Welcome!", "delivered": 2100, "opened": 990, "clicked": 220 },
{ "position": 1, "label": "Message 2", "delivered": 1912, "opened": 840, "clicked": 182 }
]
}
FieldTypeDescription
sentintegerNotifications sent in the window.
deliveredintegerNotifications delivered.
openedintegerOpens recorded.
clickedintegerClicks recorded.
convertedintegerAttributed conversions.
revenue_centsintegerAttributed revenue, in cents.
by_channelarrayPer-channel delivered/opened/clicked counts.
by_messagearrayPer-message breakdown, labeled by each message's content title (or Message N). Populated only for multi-message campaigns; empty otherwise.

Error Codes

StatusReason
401Missing or invalid credentials.
404Campaign not found, or it belongs to a different tenant.