Push
Create and manage push campaigns — APNs + FCM delivery via workflows.
Push campaigns target either a segment or all users. Scheduled campaigns kick off a PUSH_CAMPAIGN workflow with startDelay so delivery fires at the intended time regardless of API uptime.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /admin/projects/:projectId/push/campaigns | Create a campaign (draft or scheduled). |
| GET | /admin/projects/:projectId/push/campaigns | List campaigns. |
| GET | /admin/projects/:projectId/push/campaigns/:campaignId | Fetch a single campaign. |
| PATCH | /admin/projects/:projectId/push/campaigns/:campaignId | Update a draft / scheduled campaign. |
| DELETE | /admin/projects/:projectId/push/campaigns/:campaignId | Cancel + hard-delete a draft / scheduled campaign. |
| POST | /admin/projects/:projectId/push/campaigns/:campaignId/send | Trigger an immediate send. |
| POST | /admin/projects/:projectId/push/campaigns/test | Send a one-off test push (no campaign row). |
| POST | /admin/projects/:projectId/users/:userId/push-schedule | Convenience wrapper: schedule a push for a single user at a future time. |
POST /admin/projects/:projectId/push/campaigns
Create a push campaign. If scheduled_at is provided, a workflow is started with the delay and the row is stored as scheduled; otherwise the row is draft.
Request
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | Campaign name (defaults to title). |
title | string | yes | Notification title. |
body | string | yes | Notification body. |
data | object | no | Custom data payload (merged into APNs aps / FCM data). |
segment_id | uuid | no | Target a specific segment; omit to target all users. |
delivery_mode | "absolute" | "local_time" | no | Defaults to absolute. See "Local-time mode" below. |
scheduled_at | ISO-8601 | conditional | Required UTC instant for absolute mode. Must be parseable and in the future. |
local_date | YYYY-MM-DD | conditional | Required when delivery_mode='local_time'. |
local_time | HH:MM[:SS] | conditional | Required when delivery_mode='local_time'. |
Local-time mode
When delivery_mode='local_time', local_date + local_time are interpreted in each recipient's timezone (recipients without one default to UTC). Amba groups recipients by timezone and fires one delivery per bucket so 09:00 in New York is genuinely 09:00 in New York. scheduled_at MUST be omitted — sending both returns 400 CONFLICTING_SCHEDULE. Conversely, local_date / local_time may not appear when delivery_mode='absolute'.
Response 201
If the workflow start fails, the row is transitioned to failed and the response reflects that (the handler never returns a stale scheduled).
Errors
400 INVALID_DELIVERY_MODE—delivery_modeis notabsoluteorlocal_time.400 INVALID_SCHEDULED_AT—scheduled_atis not a parseable ISO-8601 string.400 SCHEDULED_AT_IN_PAST—scheduled_atis already past.400 INVALID_LOCAL_DATE—local_dateis not aYYYY-MM-DDstring (local_time mode).400 INVALID_LOCAL_TIME—local_timeis not anHH:MM[:SS]string (local_time mode).400 CONFLICTING_SCHEDULE— mixingscheduled_atwith localtime mode, or local* fields with absolute mode.500 CREATE_FAILED.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaignscurl -X POST 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns'Curl:
GET /admin/projects/:projectId/push/campaigns
List all campaigns for the project, newest first.
Response 200
Errors
500 LIST_FAILED.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaignscurl -X GET 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns'Curl:
GET /admin/projects/:projectId/push/campaigns/:campaignId
Fetch a single campaign.
Response 200
Same shape as an entry in the list response.
Errors
404 NOT_FOUND.500 FETCH_FAILED.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7Dcurl -X GET 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'Curl:
POST /admin/projects/:projectId/push/campaigns/:campaignId/send
Atomically transition status to sending and kick off the PUSH_CAMPAIGN workflow. Refuses to send a campaign that is already sending, sent, or scheduled (a scheduled campaign's delayed workflow is already queued).
Response 200
Errors
404 NOT_FOUND— campaign does not exist.409 ALREADY_SCHEDULED— campaign is already scheduled; wait for it to fire or terminate thepush-campaign-<campaignId>workflow in the the Amba support channel.409 ALREADY_SENT— campaign is alreadysendingorsent.500 SEND_FAILED— workflow start failed; campaign rolled back tofailed.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D/sendcurl -X POST 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D/send'Curl:
POST /admin/projects/:projectId/push/campaigns/test
Send a one-off test push without creating a campaign. Exists so developers can verify APNs / FCM credentials.
Request
| Field | Type | Required | Description |
|---|---|---|---|
title | string | yes | Test title. |
body | string | yes | Test body. |
app_user_id | uuid | conditional | Resolve to the user's most recent active push token. Required unless device_token+provider are supplied. |
device_token | string | conditional | Explicit APNs/FCM token. If supplied, provider must also be set. |
provider | "apns" | "fcm" | conditional | Required when device_token is set. |
data | object | no | Custom data payload. |
Response 200
Errors
400 INVALID_BODY— not valid JSON.400 MISSING_TITLE/MISSING_BODY— required fields missing.400 MISSING_TARGET— neitherapp_user_idnordevice_token.400 MISSING_PROVIDER—device_tokenset butprovidermissing.404 NO_ACTIVE_TOKEN—app_user_idhas no active push token.400 NO_TOKEN— could not resolve a token.502 SEND_FAILED/PUSH_FAILED— the push provider rejected the send.error.detailsincludesdelivery_id,provider,provider_message_id,invalid_token.500 PUSH_TEST_FAILED.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/testcurl -X POST 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/test'Curl:
PATCH /admin/projects/:projectId/push/campaigns/:campaignId
Update a campaign before send. Only draft and scheduled campaigns are mutable — anything past that (sending, sent, cancelled, failed) returns 409 ALREADY_SENT. If scheduled_at changes on a scheduled campaign, the existing workflow is terminated and re-started with the new startDelay so the campaign fires at the new time. Setting scheduled_at on a draft promotes it to scheduled.
At least one editable field must be supplied; otherwise the handler returns 400 EMPTY_UPDATE.
Request
| Field | Type | Description |
|---|---|---|
title | string | New notification title. |
body | string | New notification body. |
data | object | New custom data payload. |
segment_id | uuid | null | New target segment (or null to broadcast to all users). |
scheduled_at | ISO-8601 | New send time. Must be in the future. |
Response 200
Errors
400 INVALID_BODY— body is not JSON.400 INVALID_DATA—datais not an object.400 INVALID_SEGMENT_ID—segment_idis not a string or null.400 INVALID_SCHEDULED_AT—scheduled_atis not a parseable ISO-8601 string.400 SCHEDULED_AT_IN_PAST—scheduled_atis already past.400 EMPTY_UPDATE— no editable fields supplied.400 INVALID_CAMPAIGN_ID—campaignIdis not a UUID.404 NOT_FOUND— campaign does not exist.409 ALREADY_SENT— campaign is pastdraft/scheduled(status is included in the message).502 RESCHEDULE_FAILED— the row updated but the workflow could not be restarted; the campaign is rolled back tofailed.500 UPDATE_FAILED.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7Dcurl -X PATCH 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'Curl:
POST /admin/projects/:projectId/users/:userId/push-schedule
Convenience wrapper for the most common scheduling case: "send one push to one user at one specific time." Amba creates an ephemeral system segment containing only userId and schedules a campaign targeted at that segment for delivery at fire_at, regardless of API uptime in the interim.
The created segment is named __user_<userId>_<campaignId> and flagged as system-internal so segment-list UIs can identify and skip it. Two scheduled pushes for the same user create two distinct segments + campaigns — there is no implicit dedupe.
When to use this vs. a regular campaign: if you need timezone-aware delivery (e.g. "fire at 9am in each user's local time"), use a regular POST /push/campaigns call with delivery_mode='local_time' instead. This endpoint is for absolute-time scheduling only.
On failure: if scheduling fails (or any step after the inserts errors), the ephemeral segment, its membership, and the campaign are deleted as compensation so no orphan __user_* segments accumulate.
Request
| Field | Type | Required | Description |
|---|---|---|---|
fire_at | ISO-8601 | yes | Future delivery time. Must be parseable and in the future. |
title | string | yes | Notification title. |
body | string | yes | Notification body. |
data | object | no | Custom data payload (merged into APNs aps / FCM data). |
Response 201
Errors
400 INVALID_BODY— body is not JSON.400 INVALID_FIRE_AT—fire_atis missing or not a parseable ISO-8601 string.400 FIRE_AT_IN_PAST—fire_atis already past.400 MISSING_TITLE/MISSING_BODY— required fields missing.400 INVALID_DATA—datais not an object.400 INVALID_USER_ID—userIdis not a UUID.404 USER_NOT_FOUND—userIddoes not exist in this project's your database.500 SCHEDULE_FAILED— segment + campaign rolled back after a workflow-start (or other downstream) failure.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/users/%7B%7BuserId%7D%7D/push-schedulecurl -X POST 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/users/%7B%7BuserId%7D%7D/push-schedule'Curl:
DELETE /admin/projects/:projectId/push/campaigns/:campaignId
Cancel and hard-delete a campaign. Same ALREADY_SENT guard as PATCH — only draft and scheduled campaigns can be deleted. Any pending workflow is terminated as part of the delete; if the worker is unreachable the row is still removed (the orphaned workflow is logged as a warning).
Response 204
No body.
Errors
400 INVALID_CAMPAIGN_ID—campaignIdis not a UUID.404 NOT_FOUND— campaign does not exist.409 ALREADY_SENT— campaign is pastdraft/scheduled.500 DELETE_FAILED.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7Dcurl -X DELETE 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'Curl: