Amba

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

MethodPathDescription
POST/admin/projects/:projectId/push/campaignsCreate a campaign (draft or scheduled).
GET/admin/projects/:projectId/push/campaignsList campaigns.
GET/admin/projects/:projectId/push/campaigns/:campaignIdFetch a single campaign.
PATCH/admin/projects/:projectId/push/campaigns/:campaignIdUpdate a draft / scheduled campaign.
DELETE/admin/projects/:projectId/push/campaigns/:campaignIdCancel + hard-delete a draft / scheduled campaign.
POST/admin/projects/:projectId/push/campaigns/:campaignId/sendTrigger an immediate send.
POST/admin/projects/:projectId/push/campaigns/testSend 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

FieldTypeRequiredDescription
namestringnoCampaign name (defaults to title).
titlestringyesNotification title.
bodystringyesNotification body.
dataobjectnoCustom data payload (merged into APNs aps / FCM data).
segment_iduuidnoTarget a specific segment; omit to target all users.
scheduled_atISO-8601noFuture delivery time. Must be parseable and in the future.

Response 201

{
  "data": {
    "id": "…",
    "name": "…",
    "title": "…",
    "body": "…",
    "data": {},
    "segment_id": null,
    "status": "scheduled",
    "scheduled_at": "…",
    "sent_at": null,
    "created_at": "…"
  }
}

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_ATscheduled_at is not a parseable ISO-8601 string.
  • 400 SCHEDULED_AT_IN_PASTscheduled_at is already past.
  • 500 CREATE_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/push/campaigns' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /admin/projects/:projectId/push/campaigns

List all campaigns for the project, newest first.

Response 200

{
  "data": [
    {
      "id": "…",
      "name": "…",
      "title": "…",
      "body": "…",
      "status": "sent",
      "scheduled_at": "…",
      "sent_at": "…",
      "stats": { "sent": 1200, "delivered": 1180, "opened": 340, "failed": 20 },
      "created_at": "…"
    }
  ]
}

Errors

  • 500 LIST_FAILED.

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns
developer auth
curl -X GET 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/push/campaigns' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

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:

GET/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D
developer auth
curl -X GET 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/push/campaigns/{campaignId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

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

{ "data": { "status": "sending", "campaign_id": "…" } }

Errors

  • 404 NOT_FOUND — campaign does not exist.
  • 409 ALREADY_SCHEDULED — campaign is already scheduled; wait for it to fire or terminate the push-campaign-<campaignId> workflow in the Temporal admin UI.
  • 409 ALREADY_SENT — campaign is already sending or sent.
  • 500 SEND_FAILED — Temporal start failed; campaign rolled back to failed.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D/send
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D/send'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/push/campaigns/{campaignId}/send' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

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

FieldTypeRequiredDescription
titlestringyesTest title.
bodystringyesTest body.
app_user_iduuidconditionalResolve to the user's most recent active push token. Required unless device_token+provider are supplied.
device_tokenstringconditionalExplicit APNs/FCM token. If supplied, provider must also be set.
provider"apns" | "fcm"conditionalRequired when device_token is set.
dataobjectnoCustom data payload.

Response 200

{
  "data": {
    "sent": true,
    "delivery_id": "…",
    "provider": "apns",
    "provider_message_id": "…"
  }
}

Errors

  • 400 INVALID_BODY — not valid JSON.
  • 400 MISSING_TITLE / MISSING_BODY — required fields missing.
  • 400 MISSING_TARGET — neither app_user_id nor device_token.
  • 400 MISSING_PROVIDERdevice_token set but provider missing.
  • 404 NO_ACTIVE_TOKENapp_user_id has no active push token.
  • 400 NO_TOKEN — could not resolve a token.
  • 502 SEND_FAILED / PUSH_FAILED — the push provider rejected the send. error.details includes delivery_id, provider, provider_message_id, invalid_token.
  • 500 PUSH_TEST_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/test
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/test'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/push/campaigns/test' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

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

FieldTypeDescription
titlestringNew notification title.
bodystringNew notification body.
dataobjectNew custom data payload.
segment_iduuid | nullNew target segment (or null to broadcast to all users).
scheduled_atISO-8601New send time. Must be in the future.

Response 200

{
  "data": {
    "id": "…",
    "name": "…",
    "title": "…",
    "body": "…",
    "data": {},
    "segment_id": null,
    "status": "scheduled",
    "scheduled_at": "…",
    "updated_at": "…"
  }
}

Errors

  • 400 INVALID_BODY — body is not JSON.
  • 400 INVALID_DATAdata is not an object.
  • 400 INVALID_SEGMENT_IDsegment_id is not a string or null.
  • 400 INVALID_SCHEDULED_ATscheduled_at is not a parseable ISO-8601 string.
  • 400 SCHEDULED_AT_IN_PASTscheduled_at is already past.
  • 400 EMPTY_UPDATE — no editable fields supplied.
  • 400 INVALID_CAMPAIGN_IDcampaignId is not a UUID.
  • 404 NOT_FOUND — campaign does not exist.
  • 409 ALREADY_SENT — campaign is past draft/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 to failed.
  • 500 UPDATE_FAILED.

Try it:

PATCH/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D
developer auth
curl -X PATCH 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X PATCH '${BASE_URL}/admin/projects/{projectId}/push/campaigns/{campaignId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

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_IDcampaignId is not a UUID.
  • 404 NOT_FOUND — campaign does not exist.
  • 409 ALREADY_SENT — campaign is past draft/scheduled.
  • 500 DELETE_FAILED.

Try it:

DELETE/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D
developer auth
curl -X DELETE 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/push/campaigns/%7B%7BcampaignId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X DELETE '${BASE_URL}/admin/projects/{projectId}/push/campaigns/{campaignId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'