Push notifications

Send notifications

Send one push to every device, a list of devices or subscribers, or a group, and read the result per transport.

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" }
  }'
FieldRequiredNotes
appId, appTokenyesYour app's pair.
titleyesNotification title.
messageyesBody text. body is accepted as an alias.
audienceyesWho receives it. See Audiences.
pushDatanoA JSON object (or JSON string) delivered to the app with the notification, for deep links and ids.
bigPictureURLnoAbsolute https:// image URL, up to 2000 characters; anything else is 400 invalid_field. See Images.
environmentno"production" (default), "staging" or "development". Reaches only devices in that environment.
idempotencyKeynoUp to 200 chars. A retry with the same key writes no duplicate inbox entries. The push itself is never deduplicated.
skipInboxnotrue writes no inbox entries for this send; the push and analytics are unchanged. A non-boolean is 400 invalid_field.
Rich fieldsnosubtitle, badge, ttl, expiration, interruptionLevel, categoryId, channelId, collapseId, contentAvailable, mutableContent, sound, as in Rich notifications.

Sends are live and can't be recalled, so test with a devices audience of one device you own.

Audiences

{ "audience": { "type": "all" } }

Every registered device, with or without a subscriberId.

On Premium, a group audience, or a devices or subscribers list of more than one id, answers 403 plan_upgrade_required and sends nothing (what each plan includes).

Images

bigPictureURL also works on test sends and schedule rules. Blank means no image.

TransportOn the device
iOS (APNs)The source for the Notification Service Extension's attachment. Without an extension the notification shows title and body only.
Android (FCM)Rendered in the notification.
WebPassed to the service worker as image. Dropped first when the payload exceeds 3,993 bytes.

The device fetches the image, so host it anywhere public. If the image fails to load, the notification still arrives.

Response

{
  "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 },
  "health": {
    "apns": { "struck": 1, "dead": 0 },
    "fcm": { "struck": 2, "dead": 0 },
    "web": { "struck": 0, "dead": 0 }
  },
  "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"
}
FieldMeaning
sendIdThe nn_notification_id the push carries for open analytics. null if the analytics record couldn't be written; the send still went out.
devicesDevices the audience resolved to. A device with several tokens counts once.
attempted, accepted, failedTokens sent to, accepted by the transport, and rejected.
byTypeThe same split per transport. delivered is always null: no transport can prove delivery.
skippedAPNs and FCM tokens with no credentials saved. A setup gap, never counted against the token. Web push needs none.
healthPer transport: tokens that took a failure strike (struck) and tokens this send retired (dead).
inboxSkippedEchoes skipInbox.

Only dead-token answers (BadDeviceToken, Unregistered, FCM UNREGISTERED, web 404 or 410) add a strike. Credential and transient errors never do. Each targeted device also gets one inbox entry, written in the background, so list it after a short delay.

Errors

StatusCodeMeaning
400invalid_body, missing_field, invalid_fieldBad JSON, a missing appId, title or message, or an invalid appId, rich field, bigPictureURL, idempotencyKey or skipInbox.
400missing_audience, invalid_audienceNo audience, an unknown type, an empty id list, or a missing group key.
400invalid_environmentNot production, staging or development.
403trial_expiredThe free trial ended, or sending is paused for an unpaid invoice (details).
403plan_upgrade_requiredA group or multi-id audience on Premium.
403environment_disabledThe target environment is switched off for this app.
404app_not_foundThe app isn't one this caller can access.

Every 403 means nothing was sent. A wrong appId and appToken pair answers 401 with a plain-text message, like the rest of the API.