Send a notification
const url = 'https://api.pocketbell.dev/v1/messages';const options = { method: 'POST', headers: { 'idempotency-key': 'backup-03-2026-09-27', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, body: '{"message":"backup-03 failed with exit code 1","title":"Backup failed","markdown":true,"priority":"silent","sound":"default","url":"https://status.example.com","url_title":"View Server","thread":"backups-production","tags":["production","backup"],"ttl":3600,"replace":"deploy-4f2a91c","actions":[{"label":"Open Dashboard","url":"https://status.example.com"}],"deliver_at":"2026-09-28T02:00:00Z","attachment":"https://example.com/graph.png","callback":"https://example.com/pocketbell/callback","retry":300,"expire":3600,"options":["Approve","Deny"],"progress":0.6}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.pocketbell.dev/v1/messages \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --header 'idempotency-key: backup-03-2026-09-27' \ --data '{ "message": "backup-03 failed with exit code 1", "title": "Backup failed", "markdown": true, "priority": "silent", "sound": "default", "url": "https://status.example.com", "url_title": "View Server", "thread": "backups-production", "tags": [ "production", "backup" ], "ttl": 3600, "replace": "deploy-4f2a91c", "actions": [ { "label": "Open Dashboard", "url": "https://status.example.com" } ], "deliver_at": "2026-09-28T02:00:00Z", "attachment": "https://example.com/graph.png", "callback": "https://example.com/pocketbell/callback", "retry": 300, "expire": 3600, "options": [ "Approve", "Deny" ], "progress": 0.6 }'Queues a notification for every device the Source delivers to. Send an Idempotency-Key header to make retries safe: a repeat with the same key returns the first message instead of sending another.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”Any unique string, such as a UUID.
Example
backup-03-2026-09-27Request Bodyrequired
Section titled “Request Bodyrequired”object
The notification’s text. Longer text is kept whole in the inbox and shortened in the push.
Example
backup-03 failed with exit code 1Defaults to the Source’s name.
Example
Backup failedTrue: the message uses markdown. The apps show bold, italic, code, links and lists of lines starting with - or *; the notification itself shows the plain text. Off by default, so text like snake_case_names arrives as written.
Example
trueDefaults to the Source’s default priority.
Example
importantThe sound it plays: default is the device’s own, none plays nothing. Defaults to the Source’s sound. A Critical notification always plays the Critical sound, a desk bell, whatever this says.
Example
chimeA link shown with the notification in the apps, titled url_title. Opening it asks first.
Example
https://status.example.comExample
View ServerGroups related notifications together on the Lock Screen and in Notification Center. Without one, a Source’s notifications are grouped together.
Example
backups-productionExample
[ "production", "backup"]Seconds, from when it goes out, that Apple keeps trying a device that’s offline before dropping the notification; 0 tries once. Without it, a day. It still reaches the inbox either way.
Example
3600A key for this event. A later notification from the same Source with the same key replaces this one on screen and becomes the event’s latest in the inbox; this one stays there as an earlier update, with its time.
Example
deploy-4f2a91cUp to three buttons, on the notification and with it in the apps. Each opens its link, after asking.
object
Example
[ { "label": "Open Dashboard", "url": "https://status.example.com" }]Send at this time instead of now, up to 30 days ahead.
Example
2026-09-28T02:00:00ZPro: an image (PNG, JPEG, GIF or HEIC, up to 10 MB) shown with the notification. Pocketbell fetches it from this URL when a device shows it, and keeps nothing: your devices never contact this server, so it never sees their addresses.
Example
https://example.com/graph.pngPro, for a Critical notification or a question: POSTed when it’s acknowledged or answered, or when it expires first. Signed to the Standard Webhooks spec with the Source’s signing secret.
Example
https://example.com/pocketbell/callbackCritical only: seconds between repeats until acknowledged. Defaults to 300.
Example
300Critical, or a question: seconds after which it stops repeating or waiting for an answer. 60 to 10800; defaults to 3600.
Example
3600A question: one to three answers, shown as buttons on the notification and in the apps. The first answer, from any device, is kept, and the sender gets it from the notification’s status or a callback. Put the likely answer first: a banner may show only two.
Example
[ "Approve", "Deny"]Pro, with replace: 0 to 1. The event shows as a Live Activity on your iPhones, on the Lock Screen and in the Dynamic Island, and each notification with the same replace key updates it. 1, or a notification with the key and no progress, ends it. Updates between the first and the last arrive quietly.
Example
0.6Responses
Section titled “Responses”Accepted and queued.
object
Awaiting_answer for a question; awaiting_acknowledgment for a Critical notification, which repeats until acknowledged; scheduled when deliver_at is ahead.
Critical only: false when the account already has 10 Critical notifications repeating. This one is delivered once, without repeats, and still waits for acknowledgment until it expires.
Example
{ "id": "msg_01jz9k6q8r3tfv0x2m4n5p7s8a", "status": "queued"}The request was invalid.
object
object
Which fields were wrong, for invalid_request.
object
Example
{ "error": { "code": "invalid_request" }}Missing, malformed or revoked API key.
object
object
Which fields were wrong, for invalid_request.
object
Example
{ "error": { "code": "invalid_request" }}A Pro feature on a Free account.
object
object
Which fields were wrong, for invalid_request.
object
Example
{ "error": { "code": "invalid_request" }}Rate_limited: too many at once, retry after the Retry-After header’s seconds; monthly_limit_reached: the account’s notifications for the month are used up (a Critical one still goes out); or question_limit: the Free plan’s questions for the month are used up.
object
object
Which fields were wrong, for invalid_request.
object
Example
{ "error": { "code": "invalid_request" }}