Skip to content

Sending Notifications

  • An API key with notify or full scope
  • The App ID of the app to send notifications to

Send a notification to all active subscribers of the app.

Terminal window
curl -X POST https://api.todoke.dev/api/v1/notify \
-H "Authorization: Bearer pk_notify_key" \
-H "Content-Type: application/json" \
-d '{
"title": "New Update",
"body": "There is an update available",
"url": "https://yourapp.com/news",
"icon": "https://yourapp.com/icon.png"
}'

Specify the endpoint field to send to only that subscriber.

Terminal window
curl -X POST https://api.todoke.dev/api/v1/notify \
-H "Authorization: Bearer pk_notify_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Message for you",
"body": "This is a targeted notification",
"endpoint": "https://fcm.googleapis.com/fcm/send/..."
}'

Send to multiple endpoints in a single request using the batch API.

Terminal window
curl -X POST https://api.todoke.dev/api/v1/notify/batch \
-H "Authorization: Bearer pk_notify_key" \
-H "Content-Type: application/json" \
-d '{
"endpoints": [
"https://fcm.googleapis.com/fcm/send/endpoint1",
"https://fcm.googleapis.com/fcm/send/endpoint2"
],
"payload": {
"title": "Batch notification",
"body": "Sent to multiple users"
}
}'

Response: { "queued": 2 }

Set dry_run: true to run the same input, URL, payload-size, plan-limit, and rate-limit validation as a real send without enqueueing anything. A dry run does not update notification_logs or monthly_usage.

Terminal window
curl -X POST https://api.todoke.dev/api/v1/notify \
-H "Authorization: Bearer pk_notify_key" \
-H "Content-Type: application/json" \
-d '{
"title": "New Update",
"body": "There is an update available",
"dry_run": true
}'
# → 200 { "ok": true, "dry_run": true, "queued": 0, "would_send": 42 }

For POST /api/v1/notify, omitting endpoint returns the app’s active subscriber count. When endpoint is set, the count is 1 only if it is an active subscription for the same app; otherwise it is 0. For POST /api/v1/notify/batch, set dry_run: true at the top level of the body; matching active subscriptions are counted without duplicates. A successful dry-run response does not include send_id.

FieldTypeRequiredDescription
titlestring✅Notification title
bodystring✅Notification body
urlstring—URL to navigate to on click
iconstring—Notification icon URL
badgestring—Badge icon URL
endpointstring—If specified, sends only to this subscriber
dry_runboolean—When true, only validate and return the expected recipient count
FieldTypeRequiredDescription
endpointsstring[]✅List of endpoints (max 100 items; returns 400 TOO_MANY_ENDPOINTS if exceeded)
payload.titlestring✅Notification title
payload.bodystring✅Notification body
payload.urlstring—URL to navigate to on click (https:// only)
dry_runboolean—When true, only validate and return the expected recipient count

title and body are required. url / icon / badge only allow https:// URLs; specifying an http:// URL returns 400 INVALID_URL.

  • Payload size: the combined size of title / body / url / icon / badge is limited to 3,072 bytes (returns 413 PAYLOAD_TOO_LARGE if exceeded)
  • Monthly sends: the Free plan is limited to 30,000 notifications (returns 429 MONTHLY_LIMIT_EXCEEDED with upgrade_url if exceeded)
  • URL format: url / icon / badge must be https:// only (400 INVALID_URL)

See Error Codes and Limits for the full list of error codes.

REST API → Cloudflare Queues → Queue Consumer Worker → Browser Push

Notifications are enqueued immediately upon receiving the request. The Consumer Worker automatically starts and sends them asynchronously, so there are no timeouts even with a large number of subscribers.