Pingground

API reference

The credential-authenticated send API. Create an application and a credential in the dashboard; your subscribers hand you their recipient keys out of band.

Authentication

Every request is HTTPS JSON with Authorization: Bearer pgk_…. A credential is pgk_<lane>_<key_id>_<secret>_<checksum> — issued once in the dashboard, never retrievable again, and bound to its deployment lane. Rotation issues a successor and revokes the predecessor immediately.

A recipient key (pgr_…) addresses a person who consented to your application. It is not an authenticator: a send needs both your credential and a recipient key belonging to one of your application's subscriptions. Unknown, revoked, wrong-lane, and foreign keys all answer with the identical 404 recipient_not_found envelope.

Errors carry a stable machine code, a safe message, and a request ID. Credentials whose scopes do not permit a route receive 403 insufficient_scope.

Endpoints

  • POST /v1/notifications

    Send a notification to a consented recipient. · scope notifications:send

    • Requires an Idempotency-Key header (16–128 header-safe ASCII characters). An identical replay within 24 hours returns the original send; a different body with the same key is a 409 conflict.
    • Body: {"recipient":"pgr_…","target":{"installations":"all_active"},"draft":<typed draft>}. Target may instead list installation aliases from the resolve endpoint; it defaults to all_active.
    • Targeting resolves entirely server-side through the subscription. You never supply a device identifier or account identity, and at most 25 devices are targeted per send.
    • The response reports the honest aggregate state — including recipient_revoked and recipient_muted refusals, which never contact Apple's push service.
  • GET /v1/notifications

    Page through this application's send history. · scope sends:read

    • Cursor pagination: pass cursor=<last send_id>; the response carries next_cursor while more pages exist.
    • Recipients appear only as the public half of their recipient key plus the subscription ID.
  • GET /v1/notifications/{send_id}

    Inspect one send and its per-device attempts. · scope sends:read

    • Attempts carry pseudonymous installation aliases and the provider outcome — never a device identifier.
  • POST /v1/notifications/previews

    Build the exact payload bytes a send would carry, without sending. · scope notifications:send

    • The body is the bare typed draft. The same builder and the same server-attached attribution as the send path: preview and send bytes cannot diverge.
  • POST /v1/recipients/resolve

    What this application may know about a recipient key. · scope recipients:read

    • Body: {"recipient":"pgr_…"} — the key travels in the body, never a URL, so it stays out of request logs.
    • Returns the subscription status (active, muted, revoked), currently reachable installation aliases, and the recipient-owned limits. Never a name, device model, or any identity.
  • GET /v1/limits

    Authoritative quota usage for the calling application.

    • Reports active credentials, active subscriptions, and send usage for the short and daily windows, from authoritative counters.

The typed draft

Schema version 1. Title/subtitle up to 512 UTF-8 bytes, body up to 4096, and the final canonical payload must stay within 4096 bytes — attribution included.

{
  "schema_version": 1,
  "title": "Pingground",
  "subtitle": "",
  "body": "A notification test.",
  "sound": "default",
  "badge": null,
  "interruption_level": "active",
  "relevance_score": null,
  "category_id": null,
  "thread_id": null,
  "collapse_id": null,
  "expiration": "immediate",
  "priority": "immediate",
  "custom_data": {}
}

custom_data allows at most 16 top-level keys, four levels of nesting, and 512 bytes per string; the keys aps and pingground are reserved. Your payload carries server-attached attribution naming your application inside the pingground namespace — recipients always see who sent a notification, and you cannot alter that.

Quotas

ClassLimitOwner
Per application, short window30 sends / 60 sService
Per application, daily500 sends / 24 hService
Per subscriptiondefaults 30 / hour and 150 / dayRecipient (each adjustable from 0 up to the default)
Per recipient across all applications100 sends / 3600 sService

A refused send returns 429 send_quota_exceeded with a Retry-After header and each class's honest used, limit, and remaining counts.

Send states

These describe the provider transaction with Apple's push service — never display on a device, which no server can observe.

accepted
Apple's push service accepted every requested target.
partially_accepted
At least one target was accepted and at least one failed.
rejected
No target was accepted.
no_active_target
Consent stands, but no reachable device existed; the provider was not contacted.
recipient_revoked
Consent was withdrawn; the provider was not contacted.
recipient_muted
The recipient muted this application; the provider was not contacted.