Send Notifications
Send one push notification to your audience — every registered device, a device list, a subscriber list, or a group — and read the per-transport accepted/failed result.
One endpoint sends one notification to the devices registered with the API:
POST https://app.nativenotify.com/api/universal/notifications/send
Request
curl -X POST https://app.nativenotify.com/api/universal/notifications/send \
-H "Content-Type: application/json" \
-d '{
"appId": 123,
"appToken": "yourAppToken",
"title": "Service this Sunday",
"message": "Doors open at 9:00 — the 10:30 service is unchanged.",
"audience": { "type": "all" }
}'
| Field | Required | Notes |
|---|---|---|
appId, appToken | yes | Your app's pair. |
title | yes | Notification title. |
message | yes | Body text. body is accepted as an alias. |
audience | yes | Who this send reaches — the four shapes are below. |
pushData | no | JSON object (or a JSON string) delivered to the app with the notification (deep links, ids). |
bigPictureURL | no | Absolute https:// image URL, up to 2000 characters — see Big picture images. |
environment | no | "production" (default), "staging" or "development" — the send reaches only devices registered in it. |
idempotencyKey | no | Up to 200 chars. A retried send with the same key writes no duplicate inbox entries for devices that already have one. The push itself is never deduplicated. |
skipInbox | no | true writes no notification-inbox entries for this send — the push, the send record and analytics are unchanged. A non-boolean is a 400 invalid_field. |
| rich fields | no | subtitle, badge, ttl, expiration, interruptionLevel, categoryId, channelId, collapseId, contentAvailable, mutableContent, sound — the same fields and validation as Rich Notifications. |
Audiences
{ "audience": { "type": "all" } }
{ "audience": { "type": "devices", "deviceIds": ["A1B2C3D4-install-key", "E5F6-install-key"] } }
{ "audience": { "type": "subscribers", "subscriberIds": ["user_8241", "user_8242"] } }
{ "audience": { "type": "group", "key": "weekly-news" } }
all— every registered device, with or without asubscriberId.devices— the listeddeviceIds, whether or not they were registered with asubscriberId.subscribers— every device registered under the listedsubscriberIds.group— every device subscribed to a group, by subscriber id or device key. An unknown or empty group sends to nobody (devices: 0), not an error.
All four are filtered to the send's environment (production when omitted).
Plans:
On the Premium plan, a group audience — and a devices or subscribers list with more than one id — is a Pro feature: it answers 403 plan_upgrade_required and nothing is sent. all and one subscriber work on every plan. See what each plan includes.
Live sends:
Sends are live and cannot be recalled. A dry run means a devices audience with one device of your own. Agents are instructed to confirm title, body and audience with you before sending.
Big picture images
bigPictureURL attaches one image to the notification, on this endpoint, on test sends and on schedule rules. It must be an absolute https:// URL of at most 2000 characters — plain http:// or anything else is a 400 invalid_field, and blank or absent means no image.
| Transport | On the device |
|---|---|
| iOS (APNs) | The URL is the Notification Service Extension's attachment source. An app without an extension shows the normal title + body. |
| Android (FCM) | Android renders the image in the notification. |
| Web | The service worker passes it to showNotification, and drops it first when the payload would not fit the push service's 4,096-byte limit. |
The image is fetched by the device, so host it anywhere public — the notification still arrives if the image does not.
Response — 200
{
"ok": true,
"environment": "production",
"sendId": 1000000412,
"devices": 1284,
"attempted": 1301,
"accepted": 1298,
"failed": 3,
"byType": {
"apns": { "attempted": 640, "accepted": 639, "failed": 1, "delivered": null },
"fcm": { "attempted": 641, "accepted": 639, "failed": 2, "delivered": null },
"web": { "attempted": 20, "accepted": 20, "failed": 0, "delivered": null }
},
"skipped": { "apns": 0, "fcm": 4 },
"inboxSkipped": false,
"note": "accepted = what the transport accepted at send time. delivered stays null — APNs / FCM cannot prove delivery and this service never fabricates it.",
"checkedAt": "2026-09-23T09:30:00.000Z"
}
| Field | Meaning |
|---|---|
sendId | This send's id — the nn_notification_id the push carries for open analytics. null if the analytics record could not be written (the send still went out). |
devices | Devices the audience resolved to (a device counts once even when it has several tokens). |
attempted / accepted / failed | Tokens sent to, accepted by the transport, and rejected. |
byType | The same split per transport. delivered is always null — no transport can prove delivery. |
skipped | APNs / FCM tokens with no credentials configured for their transport. An app-config gap, never counted against the token (web push needs no credentials). |
inboxSkipped | Echo of the skipInbox choice — true = this send wrote no inbox entries, by request. |
Failures are per token: only dead-token answers (BadDeviceToken, Unregistered, FCM UNREGISTERED, web 404/410, …) add a health strike; credential or transient errors never do. Every targeted device also gets one entry in its notification inbox — written in the background, so list it after a short delay.
Errors
| Status | Code | Meaning |
|---|---|---|
400 | invalid_body, missing_field, invalid_field | Bad JSON body, missing appId / title / message, or an invalid appId, rich field, idempotencyKey or skipInbox. |
400 | missing_audience, invalid_audience | No audience, an unknown type, an empty id list, or a missing group key. |
400 | invalid_environment | environment is not production, staging or development. |
403 | trial_expired | The free trial ended, or sending is paused for an unpaid invoice (Plans & Billing) — nothing was sent. |
403 | plan_upgrade_required | A group or multi-id audience on the Premium plan — nothing was sent. |
403 | environment_disabled | The target environment is switched off for this app — nothing was sent. |
404 | app_not_found | The app is not one this caller can access. |
A wrong appId / appToken pair answers 401 with a plain-text message, like the rest of the API.