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.
statusstring—Optional. 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.