POST /v1/send
Send a personal notification to a specific subscriber.
Parameters
subscriber_id string (UUID) Subscriber UUID (from subscriber list)
text string Message text (up to 4000 characters). Required if template is not specified
format string Text format: plain (default), markdown, or html. Mutually exclusive with entities — if entities are passed, format must be empty.
entities array Structured formatting (recommended). Array of [{type, offset, length, ...}] with offset/length in UTF-16 units. Types: bold, italic, underline, strikethrough, code, pre, blockquote, spoiler, mention, text_link (with url). Immune to HTML/Markdown injection.
external_id string Alternative to subscriber_id: your own ID for the customer in your system. Resolves all active subscriptions of this customer (multi-channel one-shot send). Mutually exclusive with subscriber_id.
channels array Channel filter when resolving by external_id, array of strings (e.g. ["telegram","max"]). If empty — all active subscriptions.
channel string Single delivery channel (telegram/max) — legacy alternative to the channels array, behaves as channels: [channel].
media object Media object: {type, url}. Types: photo, video, document
buttons array Array of button rows: [[{text, url}]] or [[{text, callback_data}]]
template string Template slug instead of text
vars object Template variables: {key: value}
permission string Filter by permission: send only to subscribers with this key
Request example
Response
The response is aggregated: sent / failed are counters, details is an array per recipient. The delivery_id field is the id of the record in the project delivery log (for support reference / audit, visible in the dashboard → Deliveries); present for both successful and failed sends. The status is always 200, even if some sends failed — check details.
Idempotency
To prevent a retried request (on timeout) from creating a duplicate, pass the Idempotency-Key header — a string of 8–128 chars [A-Za-z0-9_-], unique per operation.
- Same key + same body → the same response is returned (status 200) with the X-Zapnoty-Idempotent-Replay: true header. No re-send happens.
- Same key + different body → 409 idempotency_conflict.
- A request with the same key is still being processed → 425 idempotency_in_progress, retry shortly.
Entities — structured formatting
Recommended replacement for format=html/markdown. Instead of parsing markup, the client sends an array of ranges: offset, length and format type. Immune to HTML/Markdown injection — text is never interpreted, it's sent as-is.
offset and length are measured in UTF-16 code units (like Telegram). For most languages 1 character = 1 unit; an emoji (👋) takes 2 units. Entities must be sorted by offset and not overlap.
Types: bold, italic, underline, strikethrough, code, pre, blockquote, spoiler, mention, text_link (with url field, passes private-IP SSRF check). Spoiler and mention are ignored in Max (no native counterpart).
Templates
Templates let you reuse text with variables. Created in the dashboard or via API.
Usage: pass template and vars instead of text in /v1/send.
Variables in templates use {{name}} syntax. Example: "Order {{order_id}} delivered".
Media & buttons
Attach media files and inline buttons to notifications.
Media types: photo, video, document. Pass the file URL.
Buttons are a 2D array: outer array = rows, inner array = buttons in a row.
- URL button: {"text": "Open", "url": "https://..."}
- Callback button: {"text": "Yes", "callback_data": "confirm_123"}
1 media + buttons — supported. Multiple media + buttons — not supported (Telegram limitation). Caption with media: up to 1024 characters (Telegram) / up to 4000 (Max). Text without media — up to 4000 characters.