P Push Notification Service

REST API reference

Base URL: //api.pushnotificationservice.com/v1. All responses are JSON. The API is versioned in the path. A machine-readable OpenAPI 3.1 description of everything on this page is also available.

Authentication

Create an API key in the dashboard under API keys. The key is shown once. Store it securely. Authenticate with a bearer token:

Authorization: Bearer pns_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys are pns_ followed by a 12-character prefix and a 40-character secret.

Keys are scoped to your account: you can only act on sites your account owns. Requests are rate-limited to 120/min per key. Unauthenticated requests get 401.

Send a notification

POST /v1/sites/{siteId}/notifications

FieldTypeNotes
titlestringRequired. Max 120 chars.
bodystringRequired. Max 500 chars.
urlstringClick destination. Tracked via signed redirect.
icon, imagestringOptional HTTPS image URLs.
tagsstring[]Target subscribers with any of these tags (max 10). Mutually exclusive with subscribers.
subscribersstring[]Target specific subscriber IDs (max 1000).
platformsstring[]Restrict delivery to web, ios, and/or android. Mutually exclusive with device_tokens.
device_tokensstring[]Target specific device token IDs (max 1000). Mutually exclusive with platforms.
scheduled_forISO 8601Future time to send. Omit to send immediately.

Omitting both tags and subscribers targets all active subscribers. Omitting platforms targets every platform a subscriber is on — web, iOS, and Android. Omit these fields entirely to get that behavior — an empty array (e.g. "tags": []) is rejected with a 422 rather than treated the same as omitting the field.

Quota is checked once, when you create (or restore) a notification — never again after that. A scheduled notification, or one that's already sending, always completes against its full live audience, even if more subscribers join in the meantime: we don't cancel or truncate a send you've already started because your account grew. Only creating a new notification can be rejected with a 429 for insufficient quota, and only true deliveries count against your monthly quota — a failed send costs nothing.

curl -X POST \
  //api.pushnotificationservice.com/v1/sites/SITE_ID/notifications \
  -H "Authorization: Bearer pns_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Flash sale",
    "body": "30% off for the next hour.",
    "url": "https://example.com/sale",
    "tags": ["offers"]
  }'
{
  "data": {
    "id": "9b1f...",
    "title": "Flash sale",
    "status": "queued",
    "counts": { "targeted": 0, "delivered": 0, "displayed": 0, "failed": 0, "clicked": 0 }
  }
}

Delivery is asynchronous: the send returns queued (or scheduled) and the counts fill in as workers deliver. Poll the campaign endpoint below for live counts. delivered counts push-service acceptance; displayed counts notifications the service worker actually showed on a device.

Other notification endpoints

Method & pathDescription
GET /v1/sites/{siteId}/notificationsList campaigns (newest first, 25 per page, ?page=).
GET /v1/sites/{siteId}/notifications/{id}Fetch one campaign with live delivery counts.

Subscribers

Method & pathDescription
GET /v1/sites/{siteId}/subscribersList subscribers. Filters: ?status=active, ?tag=news, ?per_page= (default 50, max 200).
POST /v1/sites/{siteId}/subscribers/{id}/tagsAdd/remove tags: {"add":["vip"],"remove":["trial"]} (max 20 each).
DELETE /v1/sites/{siteId}/subscribers/{id}Unsubscribe a subscriber.
GET /v1/sites/{siteId}/tagsList tags with subscriber counts.

Mobile device tokens

Native iOS and Android apps register their own push token so campaigns can target them alongside (or instead of) web subscribers. These endpoints are public — no API key — the same way the SDK's web subscription endpoints are, since a native app has no Origin header for CORS to check against. Each site needs its APNs and/or FCM credentials configured once in the dashboard before sends actually deliver; registration itself is accepted either way, so a token is never lost to a timing gap.

Method & pathDescription
POST /v1/sites/{siteId}/device-tokensRegister (or refresh) a device token: {"platform":"ios","token":"..."}. Idempotent; pass old_token to migrate a rotated token in place.
POST /v1/sites/{siteId}/device-tokens/unregisterOpt out: {"platform":"ios","token":"..."}. Idempotent and quiet about unknown tokens.
curl -X POST \
  //api.pushnotificationservice.com/v1/sites/SITE_ID/device-tokens \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "ios",
    "token": "the 64-character hex APNs device token"
  }'

platform is ios or android. iOS tokens are 64-200 hex characters; Android (FCM) tokens are long base64url-ish strings. An optional bundle_id field, if sent, must match the site's configured APNs bundle ID or the request is rejected — a lightweight check against token stuffing, since there's no Origin header to rely on here.

tags cannot target device tokens (there's no per-device tagging yet) — a tag-scoped send only ever reaches web subscribers, and combining tags with platforms: ["ios"]/["android"] is rejected as a 422. The image field on a notification displays automatically on Android; on iOS it requires the app to ship its own Notification Service Extension (a standard APNs mutable-content requirement).

Signed links in notifications

Every delivered notification carries signed URLs generated per campaign or subscriber. You never build these yourself, but you'll see them in traffic and logs:

Method & pathDescription
GET /c/{campaignId}Click-through redirect. Counts the click, then 302s to the campaign's url (or the site's domain). No cookies, no per-user history.
GET /d/{campaignId}Display receipt pinged by the service worker after it shows the notification. Campaign-scoped, carries no subscriber identity.
GET /unsubscribe/{subscriberId}One-click unsubscribe page. Works without JavaScript or CORS.

All of them verify a URL signature and reject tampered links with 403. The browser-facing subscription endpoints used by the SDK snippet (/v1/sites/{siteId}/config, /v1/sites/{siteId}/subscriptions…) are internal to the snippet and documented on the snippet page.

Errors & limits

StatusMeaning
401Missing or invalid API key.
403Account suspended.
404Site or resource not found, or not owned by your account.
422Validation error (see errors in the body).
429Rate limit hit, or monthly push quota exceeded.