/v1/send
Bearer auth. See Concepts → Authentication.
POST /v1/send
What it does: Creates a broadcast and queues it for asynchronous fan-out. Returns immediately with a broadcast_id — actual delivery happens in the background.
When to use it: Whenever you want to send a push. This is the main "do work" endpoint.
Authentication: Bearer.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
target | object | yes | Discriminated union — see below. |
notification.title | string (1–256 chars) | yes | Notification title — visible to the user. |
notification.body | string (1–2048 chars) | yes | Notification body. |
notification.icon | string | no | Small icon shown alongside the notification. Either an HTTPS URL (≤ 2048 chars, used as-is) or a base64 data:image/png|jpeg|webp URL (≤ 256 KB) we host for you and serve from litepush.dev/cdn/icons. Must be square (1:1) — non-square icons are rejected. Available on every plan. If omitted, the project's default icon (set in the dashboard) is used. |
notification.image | string | no | Hobby+ only. Large banner image shown below the body on platforms that support it (Chrome desktop/Android). Same formats as icon — an HTTPS URL (≤ 2048 chars) or a base64 data:image/* URL (≤ 1 MB) we host for you. Sending it on a Free plan returns 403 image_requires_upgrade. |
notification.url | string (URL, ≤ 2048 chars) | no | URL to open when the user clicks. Defaults to / (your site root) when omitted. |
ttl | number (seconds, 0–2 419 200) | no | Every plan. How long the push service keeps trying to deliver while the device is offline. 0 = deliver only if the device is connected right now; larger values survive longer offline windows. Defaults to 86 400 (24 hours). Max is 4 weeks. Maps to the RFC 8030 TTL header. |
urgency | string | no | Every plan. Delivery priority hint: very-low, low, normal, or high. The push service uses it to trade battery against promptness — high wakes an idle device sooner; very-low defers until it's charging/on Wi-Fi. Defaults to normal. Maps to the Urgency header. |
topic | string (≤ 32 chars, A–Z a–z 0–9 _ -) | no | Every plan. Collapse key. A newer broadcast with the same topic replaces an older, still-undelivered one in the push gateway — so an offline device that comes back online wakes to only the latest (e.g. a live score, an unread count). Omit it for no collapsing. Maps to the Topic header. |
scheduled_at | number (Unix ms) | no | Plus+ only. Schedule the broadcast for a future time instead of sending now. Must be in the future and within 90 days. The push budget is reserved at fire time (so it counts against the send month), and delivery fires within ~5 minutes of the scheduled time. Returns 202 with { broadcast_id, scheduled_at }. Cancel before it fires with DELETE /v1/broadcasts/:id. |
target shapes:
| Shape | Effect | Behavior when nothing matches |
|---|---|---|
{ "type": "all" } | Every active subscriber in the project. | If no subscribers exist, broadcast is accepted with delivered: 0. |
{ "type": "user", "external_id": "user_42" } | Every subscriber whose external_id matches — covers users with multiple devices. | Returns 202 with broadcast_id. Audience is 0; no pushes delivered. This is intentional — the user may not have subscribed yet, or may subscribe to a second device later. The broadcast row stays as a record of intent. |
{ "type": "group", "group_id": "grp_xxx" } | All members of one named group. See /v1/groups. | A non-existent or wrong-project group_id returns 404 group_not_found before the broadcast is created — no silent zero-audience send. An existing group with no members sends to no one and returns 202 normally. |
Example — broadcast to everyone:
curl -X POST https://api.litepush.dev/v1/send \
-H "Authorization: Bearer lpk_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"target": { "type": "all" },
"notification": {
"title": "New post on the blog",
"body": "We just shipped Web Push support — read more",
"url": "https://myblog.com/posts/web-push"
}
}'
Example — broadcast to one user (e.g. transactional):
curl -X POST https://api.litepush.dev/v1/send \
-H "Authorization: Bearer lpk_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"target": { "type": "user", "external_id": "user_42" },
"notification": {
"title": "Order shipped",
"body": "Your order #4912 is on its way.",
"url": "https://shop.example.com/orders/4912"
}
}'
Example — time-sensitive alert that collapses an earlier one:
curl -X POST https://api.litepush.dev/v1/send \
-H "Authorization: Bearer lpk_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"target": { "type": "user", "external_id": "user_42" },
"notification": { "title": "Score: 2–1", "body": "United just scored." },
"urgency": "high",
"ttl": 600,
"topic": "match-1234"
}'
Here urgency: "high" asks the push service to wake the device promptly, ttl: 600 drops the update if it can't be delivered within 10 minutes (a stale score is worse than none), and topic: "match-1234" means the next score update for the same match replaces this one instead of stacking.
Success — 202 Accepted:
{ "broadcast_id": "bdc_01HXM..." }
202 not 200 because fan-out is asynchronous. The broadcast row in the dashboard updates with delivered / failed counts as the queue consumer works through subscribers.
Errors:
400(validation) — body shape wrong, title/body empty, target type unknown, ausertarget'sexternal_idover 256 chars or containing characters outsideA–Z a–z 0–9 _ -,ttlout of range (0–2 419 200),urgencynot one of the four levels, ortopicover 32 chars / containing characters outsideA–Z a–z 0–9 _ -.400 invalid_icon/400 invalid_image— adata:URL icon/image was malformed, empty, an unsupported type (not PNG/JPEG/WebP), or over its size cap (icon 256 KB, image 1 MB).invalid_iconis also returned when the icon isn't square (1:1).400 invalid_scheduled_at—scheduled_atis in the past or more than 90 days out.401 invalid_api_key.403 monthly_push_limit_reached— sending this broadcast would push your account past its plan cap. Returned with the cap and the broadcast's estimated audience in the message.403 scheduling_requires_upgrade—scheduled_atwas supplied on a plan below Plus.403 image_requires_upgrade—notification.imagewas supplied on the Free plan (Hobby+ only).404 group_not_found—target.typeisgroupbut thegroup_iddoesn't exist in this project.429 rate_limited.
A
grouptarget with a bad ID is rejected up-front with404. Ausertarget whoseexternal_idhas no active subscribers is not an error — it returns202with zero deliveries, because sending to a user who hasn't subscribed yet (or subscribes later) is a legitimate transactional pattern. Watch the broadcast'sdeliveredcount (dashboard ordeliveredwebhook) to confirm reach.