starti.app
REST API

Push Notifications

Send push notifications and badge updates to users, devices or topics, and list the users who can receive them.

Send Notification

POST /push-notifications/send

Send notifications or badge updates to users, devices or topics. The request body is an array of notification objects, allowing you to send multiple notifications in a single request.

Each notification targets exactly one of userIds, deviceIds or topics — they cannot be combined in the same object. The userIds correspond to the IDs registered via startiapp.User.registerId() on the client side, and the deviceIds to the installation IDs from startiapp.App.deviceId().

An API key belongs to one environment: Production (the app your users have installed) or Test (the test version of the app your testers install). Every call only sees that environment's devices: userIds and deviceIds resolve to devices running that app, topics reach that app's subscribers, and a user who only has the other app is skipped with no_active_device. Use a Test key from your staging system and a Production key from production — the key decides, so a staging configuration can never reach real users. Keys created before environments were introduced are Production keys. See Create and manage API keys.

Request body

The request body is a JSON array of notification objects.

[
  {
    "userIds": ["user123", "user456"],
    "title": "New Message",
    "body": "You have received a new message",
    "openToUrl": "https://example.com/message/123",
    "badgeCount": 5
  }
]

Prop

Type

silentInForeground is for apps that present the message themselves. With it set, a user who already has the app open sees no system notification — but the app still receives the notificationReceived event with the title and body, so your page can show it in whatever way fits. A user whose app is closed or in the background sees the notification as always. It needs no app update: the flag is handled by the app shell already in the stores.

tag is for notifications your page can make redundant. When the user opens the app on their own and sees what the notification announced — an order that shipped, a result that is ready — the notification would otherwise stay on the lock screen as unread. Send it with a tag (say order-8812), and call clearNotifications with that tag once the page has shown the thing. Two rules come with the name: a later send with the same tag to the same device replaces the earlier notification rather than adding a second one, and a device that is offline receives only the newest of them. So use a tag per thing announced, not per kind of message, unless replacing is what you want. The tag also arrives in the notificationReceived event. Removing needs App Shell 4.258 or later; on older builds the notification stays until the user dismisses it.

Exactly one of userIds, deviceIds or topics must be provided. To send a visible notification, include both title and body. To update only the badge count, omit title and body and provide badgeCount.

Examples

curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '[
    {
      "userIds": ["user123"],
      "title": "New Message",
      "body": "You have received a new message",
      "openToUrl": "https://example.com/message/123"
    },
    {
      "userIds": ["user456"],
      "badgeCount": 5
    }
  ]' \
  https://api.starti.app/v1/push-notifications/send
curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '[
    {
      "deviceIds": ["00000000-0000-0000-0000-000000000000"],
      "title": "Signed in on a new device",
      "body": "Tap to review your recent activity"
    }
  ]' \
  https://api.starti.app/v1/push-notifications/send
curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '[
    {
      "topics": ["general", "announcements"],
      "title": "System Update",
      "body": "The system will be updated tonight",
      "openToUrl": "https://example.com/updates"
    }
  ]' \
  https://api.starti.app/v1/push-notifications/send
curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '[
    {
      "userIds": ["user123"],
      "badgeCount": 5
    }
  ]' \
  https://api.starti.app/v1/push-notifications/send
curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '[
    {
      "userIds": ["user123"],
      "title": "New Message",
      "body": "You have received a new message",
      "silentInForeground": true
    }
  ]' \
  https://api.starti.app/v1/push-notifications/send
curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '[
    {
      "userIds": ["user123"],
      "title": "Your visualisation is ready",
      "body": "Open the app to see your car with the new wheels",
      "openToUrl": "https://example.com/renders/8f3a2c",
      "tag": "render-8f3a2c"
    }
  ]' \
  https://api.starti.app/v1/push-notifications/send

Then, on the page that shows the render:

startiapp.PushNotification.clearNotifications({ tags: ["render-8f3a2c"] });

Responses

Every response carries an x-request-id header — the empty 200 of a fully delivered batch included. It identifies the call, so you have something to record alongside your own logs and something concrete to quote in a support request.

If your brand has the notification log switched on in Manager, that same id is what a call is listed under there: the body you sent, the recipients it resolved to, and the outcome for each of them. Nothing else about the request or the response changes — the header is an addition, and every field documented below is unaffected.

Every target in the request gets its own outcome in the results array, each echoing what you addressed it with — userId, deviceId or topics:

  • delivered — handed to the push service. deviceCount says how many devices it went to.
  • skipped — nothing to deliver to. reason says why; currently always no_active_device.
  • failed — delivery was attempted and failed. message carries the underlying error.

The status code follows the worst outcome in the batch: 207 if any target failed, otherwise 200. It tells you whether anything needs acting on — not whether every recipient was reached — so skipped targets never make a request 207.

no_active_device means the user has no device registered, or every device behind them has lost its push token — typically because the app was uninstalled or notifications were turned off — or their only device runs the other environment's app (a Test key cannot reach the production app, and vice versa). Retrying will not help, so treat it as a signal to stop sending to that user rather than as a failure. They become reachable again the moment the app registers a new token. When we know when we last heard from the device, unreachableSince tells you how long they have been gone.

List Recipients

GET /push-notifications/recipients

Returns the user IDs that can receive a push notification right now. Use it when you keep your own list of users and want to send only to the ones that will actually get the message, instead of paying a skipped result for every user who has not opted in.

A user is included when at least one of their devices:

  • is signed in with that user ID via startiapp.User.registerId(),
  • still holds a push token — the app is installed and has registered with the push service,
  • has recorded that the user granted the notification permission, and
  • runs the app that matches the key's environment (production or test).

The list is maintained as devices change, so it reflects what is true right now, not a snapshot from earlier in the day.

Fetch the list right before each send rather than caching it — users opt in and uninstall all the time. Then keep each POST /push-notifications/send to 500 userIds or fewer: a campaign to every recipient is one GET, split into batches of 500, each sent as its own request.

Example

curl \
  -H "x-api-key: YOUR_API_KEY" \
  https://api.starti.app/v1/push-notifications/recipients

Responses

On this page