Notifications API
POST /v1/notifications
Secret key, scope notifications:send. Rate-limited per key. Accepts Idempotency-Key.
{
"target": { "externalIds": ["user_123", "user_456"] },
"content": {
"en": {
"title": "Your order is on its way",
"body": "Order #48213 ships today. Track it in the app.",
"url": "acme://orders/48213",
"image": "https://cdn.acme.example/orders/48213.jpg",
"actions": [{ "id": "track", "title": "Track" }, { "id": "help", "title": "Help", "url": "acme://support" }]
},
"tr": { "title": "Siparişin yola çıktı", "body": "#48213 bugün kargoda." },
"_default": "en"
},
"data": { "screen": "order", "id": "48213" },
"ttl": 86400,
"collapseId": "order-48213",
"priority": "high",
"presentation": { "sound": "default", "badge": 1, "threadId": "orders", "icon": "https://cdn.acme.example/icon.png" },
"bypass": { "quietHours": true }
}
target — exactly one selector
| Selector | Type | Limit |
|---|---|---|
externalIds | string[] | 1–1000 |
userIds | ObjectId[] | 1–1000 |
subscriptionIds | ObjectId[] | 1–1000 |
segment | segment node | — |
segmentId | ObjectId of a saved segment | — |
all | true | — |
Users resolve to all their opted-in, non-invalidated devices.
Other fields
| Field | Type | Default | Notes |
|---|---|---|---|
content | content map | one of content / templateId / silent | Content. Placeholders allowed. |
templateId | ObjectId | — | Copy and defaults from a template; fields in the request override |
silent | boolean | false | Background push, no alert; content optional; web skipped. Silent pushes |
data | Record<string,string> | — | keys ≤ 64, values ≤ 1024 chars |
ttl | int seconds | 259200 | 0 – 2 419 200 (4 weeks) |
collapseId | string ≤ 64 | — | |
priority | "high" | "normal" | "high" | delivery urgency (APNs 10/5, FCM high/normal) |
presentation | object | — | sound, badge (0–99999), threadId, icon, interruptionLevel (passive|active|time-sensitive|critical), relevanceScore (0–1), channelId |
bypass | { frequencyCap?, quietHours? } | — | Delivery rules |
Response 202 Accepted
{ "id": "66f3…", "status": "sending" }
id is a campaign id (transactional: true). Poll GET /v1/campaigns/:id/stats for the outcome, or subscribe to webhooks.
Errors
| Status | Code | Why |
|---|---|---|
| 401 | unauthorized | missing/invalid key |
| 403 | forbidden | public key, or missing notifications:send |
| 422 | invalid_body | see details[] — most often target (zero or two selectors) or content._default |
| 429 | rate_limited | per-key limit |
| 409 / 422 | idempotency_in_progress / idempotency_key_reused | Idempotency |
| 503 | database_unavailable | Mongo down |
Examples
Everyone, silent data-only refresh (badge only, no sound):
{ "target": { "all": true }, "content": { "en": { "title": "Sync", "body": "Refreshing" }, "_default": "en" },
"presentation": { "sound": "none", "badge": 0 }, "priority": "normal", "bypass": { "frequencyCap": true, "quietHours": true } }
A segment:
{ "target": { "segment": { "and": [ { "field": "tags.plan", "op": "eq", "value": "trial" }, { "field": "createdAt", "op": "lt", "value": "-13d" } ] } },
"content": { "en": { "title": "Your trial ends tomorrow", "body": "Keep your streak — upgrade today.", "url": "acme://upgrade" }, "_default": "en" } }