Push
Create and manage push campaigns — APNs + FCM delivery via Temporal workflows.
Push campaigns target either a segment or all users. Scheduled campaigns kick off a Temporal PUSH_CAMPAIGN workflow with startDelay so delivery fires at the intended time regardless of API uptime.
Source: apps/api/src/routes/admin/push.ts.
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/push/campaigns
Create a push campaign. If scheduled_at is provided, a Temporal 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. |
scheduled_at | ISO-8601 | no | Future delivery time. Must be parseable and in the future. |
Response 201
If the Temporal start fails, the row is transitioned to failed and the response reflects that (the handler never returns a stale scheduled).
Errors
400 INVALID_SCHEDULED_AT—scheduled_atis not a parseable ISO-8601 string.400 SCHEDULED_AT_IN_PAST—scheduled_atis already past.500 CREATE_FAILED.
Try it:
/admin/projects/%7B%7BprojectId%7D%7D/push/campaignscurl -X POST 'https://api.amba.dev/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/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/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 Temporal admin UI.409 ALREADY_SENT— campaign is alreadysendingorsent.500 SEND_FAILED— Temporal 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/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/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 Temporal 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 Temporal 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/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'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 Temporal 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/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'Curl: