POST
/api/public/v1/campaignsCreate a campaign
Starts a campaign as a draft — always, whatever the channel. An SMS one has a carrier registration to get through before it can send, and an email one still wants a look at the audience count first. **Nothing here reaches anybody**; sending is a separate call. The channel is fixed at creation because the audience filter, the content limits and the whole SMS registration hang off it.
Required scope
The access token must carry this scope. A token missing it gets 403 Forbidden. Grant them to an API client under Settings → Company → API, and to a teammate under Employee → Permissions.
campaigns:write
Request Body
| Property | Type | Description |
|---|---|---|
locationId | string | The location. |
name | string | Internal name. |
channel | string | Fixed once set. |
subject | string | Required for EMAIL, ignored for SMS. |
body | string | The message. SMS has a much shorter limit than email. |
audience | object | `{ lifecycleStages, inactiveDays, createdWithinDays, neverContacted }` — all four keys required, the numeric two nullable. |
POSTExample request
curl
curl -X POST "https://www.pxb.app/api/public/v1/campaigns" \
-H "Authorization: Bearer your_access_token" \
-H "Content-Type: application/json" \
-d '{
"locationId": "loc_abc123",
"name": "Autumn service reminder",
"channel": "EMAIL",
"subject": "Time for your autumn service",
"body": "Hi {{name}} — you are due for a service.",
"audience": {
"lifecycleStages": [
"CUSTOMER"
],
"inactiveDays": 180,
"createdWithinDays": null,
"neverContacted": false
}
}'200Example response
json
{
"message": "Campaign created successfully!",
"content": {
"campaign": {
"id": "cmp_3311",
"locationId": "loc_abc123",
"name": "Autumn service reminder",
"channel": "EMAIL",
"status": "DRAFT",
"subject": "Time for your autumn service",
"body": "Hi {{name}} — you are due for a service.",
"audience": {
"lifecycleStages": [
"CUSTOMER"
],
"inactiveDays": 180,
"createdWithinDays": null,
"neverContacted": false
},
"scheduledAt": null,
"startedAt": null,
"sentAt": null,
"createdAt": "2026-08-26T12:00:00.000Z",
"updatedAt": "2026-08-26T12:00:00.000Z"
}
}
}In the console
The same thing done by hand, for anyone comparing the API against the screen they already use.