Skip to content

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.

Send now or retry safely

Sends without an idempotency header are the simple default: each HTTP request is a new notification. Add a key only when a caller may retry the same uncertain request.

Simple send

curl --request POST 'https://api.pingground.app/v1/notifications' \
  --header 'Authorization: Bearer <PINGGROUND_SEND_CREDENTIAL>' \
  --header 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "recipient": "<PINGGROUND_RECIPIENT_KEY>",
  "target": {
    "installations": "all_active"
  },
  "draft": {
    "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": {}
  }
}
JSON
Advanced · safe retries

Reuse one stable key only while retrying the same logical send. Changed recipient, target, or draft content with that key returns idempotency_conflict. Use a fresh key for a new notification.

curl --request POST 'https://api.pingground.app/v1/notifications' \
  --header 'Authorization: Bearer <PINGGROUND_SEND_CREDENTIAL>' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: <STABLE_KEY_FOR_THIS_SEND>' \
  --data-binary @- <<'JSON'
{
  "recipient": "<PINGGROUND_RECIPIENT_KEY>",
  "target": {
    "installations": "all_active"
  },
  "draft": {
    "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": {}
  }
}
JSON

Endpoints

  • POST/v1/mediascope notifications:send

    Upload a static JPEG or PNG notification image.

    • Raw image body, at most 5 MiB, 4096 pixels per side and 8 million pixels. The server strips metadata and resizes within 2048 pixels per side.
    • Returns media_id, url, width, height, and expires_at. Use media_id in a schema-2 draft. Upload once and reuse it for send retries.
    • 100 uploads per application per rolling 24 hours, 100 MiB live/reserved storage, expiry after 30 days. Anyone with the URL can download until expiry or deletion; downloaded copies cannot be recalled.
  • DELETE/v1/media/{media_id}scope notifications:send

    Disable an application's hosted notification image.

    • Returns 204. Access stops immediately; byte cleanup is retried separately. Deletion does not refund daily upload admissions.
  • POST/v1/notificationsscope notifications:send

    Send a notification to a consented recipient.

    • Idempotency-Key is optional for ordinary sends and required for schema-3 questions. Reuse one 16–128 character header-safe key for safe retries: an identical replay within 24 hours returns the original send and question deadline, while changed content 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 aggregate state — including recipient_revoked and recipient_muted refusals, which never contact Apple's push service.
  • POST/v1/integrations/uptime-kuma/notificationsscope notifications:send

    Send Uptime Kuma's preset Webhook JSON to a consented recipient.

    • Set X-Pingground-Recipient to the display-once pgr_… recipient key. Missing, malformed, wrong-lane, and foreign keys all return 404 recipient_not_found.
    • The preset msg becomes the body; monitor.name becomes the title, with Uptime Kuma as the fallback. Unknown integration fields are ignored.
    • Targets all active installations. accepted or partially_accepted returns 200; consent and target refusals return 409; a fully rejected provider transaction returns 502 notification_rejected.
    • Idempotency-Key is optional with the same safe-retry behavior as the generic send route. The standard Kuma setup omits it.
    • If an identical supplied-key replay arrives while the original send is still processing, the adapter returns 409 send_in_progress; retry the same key until it reaches a terminal state.
  • GET/v1/notificationsscope sends:read

    Page through this application's send history.

    • 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}scope sends:read

    Inspect one send and its per-device attempts.

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

    Build the exact payload bytes a send would carry, without sending.

    • 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.
  • GET/v1/notifications/{send_id}/responsescope sends:read

    Inspect a question's state and accepted answer.

    • Returns open, answered, expired, or cancelled plus the deadline and definition. It exposes no recipient or device identity.
  • POST/v1/notifications/{send_id}/cancelscope notifications:send

    Cancel an open question.

    • Requires Idempotency-Key. The first terminal transition remains final; competing calls return the recorded result.
  • POST/v1/recipients/resolvescope recipients:read

    What this application may know about a recipient key.

    • 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

    Quota usage for the calling application.

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

Uptime Kuma adapter

Choose Webhook, POST, and the preset application/json body in Uptime Kuma. Put the send-only credential and recipient key in the two static headers documented above. The adapter sanitizes and fits the alert, then runs the same consent, quota, targeting, attribution, history, and provider path as a generic send.

The typed draft

Schemas 1–3. Title/subtitle up to 512 UTF-8 bytes, body up to 4096, and the final canonical payload must stay within 4096 bytes, including attribution, images, links, questions, and routing.

{
  "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.

Links and images (schema 2)

Set schema_version to 2 and keep the base draft fields above. Optional url is an HTTPS destination up to 512 UTF-8 bytes; http is accepted only for a host unreachable from the public internet (loopback, RFC1918, CGNAT, link-local, IPv6 unique-local, or a .local, .internal, .lan, home.arpa, or single-label name) written exactly as the URL parser reads it. url_title is an optional label up to 80 bytes and requires url. Add image_url (HTTPS only, up to 1024 bytes) or media_id from an upload, never both. Credentials, controls, and malformed URLs are refused. Schema 1 remains supported unchanged.

Default notification taps open the message. A link without an explicit category gets an Open Link action. An explicit category is preserved with a warning if it prevents that action. Preview byte counts include same-length routing placeholders. Oversized rich requests are refused without truncation or downgrade.

Questions (schema 3)

Set schema_version to 3 and add question with kind choice, yes_no, or text. Choice questions require 2–4 stable IDs with labels up to 80 UTF-8 bytes. Yes/No always uses no then yes; text replies allow up to 2,000 UTF-8 bytes. expires_in_seconds defaults to 1,800 and accepts 60–86,400. A caller category is refused because Pingground owns the registered action category.

The first server-accepted answer wins across devices and web. Poll the response endpoint or configure a signed callback in the application dashboard.

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 used, limit, and remaining counts.

Send states

The outcome recorded for each send.

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.