Skip to main content

REST API overview

Base URL: your deployment, e.g. https://push.example.com. All endpoints are under /v1. Bodies are JSON; validation is strict and errors say which field.

An interactive OpenAPI browser is served at /docs on the API itself.

Authentication

KeyHeaderPrefixFor
Secret keyAuthorization: Bearer sk_live_<appId>_<secret>sk_your backend, CI. Everything its scopes allow.
Public keyx-app-key: pk_live_<appId>_<secret>pk_SDKs. Register a device, update that device's user.
(none)POST /v1/e/:kind/:msgId — the signed msgId is the credential.

The app id is embedded in the key, so verification is one indexed lookup plus a bcrypt compare, cached for API_KEY_CACHE_TTL (60 s). A public key on a server endpoint answers 403 forbidden regardless of scopes. Keys are created and revoked in Settings › API keys.

Scopes

ScopeEndpoints
subscriptions:writePOST/PATCH/DELETE /v1/subscriptions/*
subscriptions:readPOST /v1/segments/preview
users:read / users:writeGET / PATCH,DELETE /v1/users/*
notifications:sendPOST /v1/notifications
campaigns:readGET /v1/campaigns/:id/stats
campaigns:writePOST /v1/campaigns, /send, /cancel, /experiment/winner

Rate limits

Fixed 60-second windows, per API process:

BucketDefaultKey
subscribe / rotateSUBSCRIBE_RATE_LIMIT = 10/minclient IP
/v1/notificationsSEND_RATE_LIMIT = 100/minAPI key
/v1/e/*EVENT_RATE_LIMIT = 600/minclient IP

Over the limit: 429 rate_limited with the seconds to wait in the message. Client IP comes from x-forwarded-for (first hop) or x-real-ip — make sure your proxy sets one.

Errors

One shape everywhere:

{
"error": {
"code": "invalid_body",
"message": "notification payload is invalid",
"details": [
{ "path": "content._default", "message": "_default \"fr\" is not one of: en, tr" },
{ "path": "target", "message": "provide exactly one of externalIds, userIds, subscriptionIds, segment or all" }
]
}
}

Full code list: Errors.

Idempotency

POST /v1/notifications and POST /v1/campaigns accept Idempotency-Key (≤ 255 chars); replays within 24 h return the original response with Idempotent-Replayed: true. Details: Idempotency.

Availability

  • GET /health200 {"status":"ok","db":"ready"} or 503 while MongoDB is unreachable.
  • While Mongo is down every data endpoint answers 503 database_unavailable rather than queueing.
  • Without Redis the API does not start at all.

Endpoint index

Method & pathKeyScopePage
POST /v1/subscriptionspk / sksubscriptions:writeSubscriptions
PATCH /v1/subscriptions/:idpk / sksubscriptions:write
POST /v1/subscriptions/:id/loginpk / sksubscriptions:write
POST /v1/subscriptions/:id/logoutpk / sksubscriptions:write
PATCH or POST /v1/subscriptions/:id/userpk / sksubscriptions:write
DELETE /v1/subscriptions/:idpk / sksubscriptions:write
POST /v1/subscriptions/rotatepk / sksubscriptions:write
POST /v1/subscriptions/:id/sessionpk / sksubscriptions:write
GET /v1/users/:id · /by-external-id/:externalIdskusers:readUsers
PATCH /v1/users/:id · /by-external-id/:externalIdskusers:write
DELETE /v1/users/:id · /by-external-id/:externalIdskusers:write
POST /v1/notificationssknotifications:sendNotifications
POST /v1/campaignsskcampaigns:writeCampaigns
POST /v1/campaigns/:id/send · /cancelskcampaigns:write
POST /v1/campaigns/:id/experiment/winnerskcampaigns:write
GET /v1/campaigns/:id/statsskcampaigns:read
POST /v1/segments/previewsksubscriptions:readSegments
GET /v1/segments · GET /v1/segments/:id · POST /v1/segments/:id/countsksubscriptions:read
POST /v1/segments · PUT · DELETE /v1/segments/:idskcampaigns:write
GET /v1/templates · GET /v1/templates/:idskcampaigns:readTemplates
POST /v1/templates · PUT · DELETE /v1/templates/:idskcampaigns:write
POST /v1/e/:kind/:msgIdnoneEvents
GET /healthnone
/v1/admin/*ADMIN_API_TOKENdashboard only; 404 when unset

Conventions

  • Ids are 24-hex MongoDB ObjectIds as strings.
  • Timestamps are ISO 8601 UTC.
  • PATCH bodies are partial and must contain at least one field.
  • The admin surface (/v1/admin) is not a public API: it exists for the dashboard, needs the shared token, and has no stability promise.