API Documentation

Quick start

Three steps to your first notification.

1

1. Create a project

Sign up with Yandex or VK, create a project in the dashboard.

2

2. Get an API key

Copy your API key (format: zn_aBcDeFgH...) from project settings.

3

3. Send your first request

Use curl or any HTTP client:

curl -X POST https://api.zapnoty.com/v1/send
-H "Authorization: Bearer zn_aBcDeFgH..."
-H "Content-Type: application/json"
-d '{
"user_ref": "user_123",
"text": "Hello!"
}'

Authentication

All API requests require a Bearer token in the Authorization header.

Header format:

Authorization: Bearer zn_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456

API key format: zn_ + 32 characters. Keys are created in the project dashboard.

Never pass the key in URL or query parameters. Use only the Authorization header.

POST /v1/send

Send a personal notification to a specific subscriber by user_ref.

Parameters

user_ref string required

Subscriber identifier in your system

text string required

Message text (up to 4096 characters)

format string

Text format: plain (default), markdown, or html

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}

Request example

POST /v1/send
 
{
"user_ref": "user_123",
"text": "Order #1042 shipped!",
"format": "markdown",
"buttons": [[{
"text": "Track",
"url": "https://example.com/track/1042"
}]]
}

Response

{
"ok": true,
"message_id": "msg_abc123"
}

OTP (one-time passwords)

Send and verify confirmation codes via messenger.

POST /v1/otp/send

Generates a 6-digit code and sends it to the subscriber.

user_ref string required

Subscriber identifier

POST /v1/otp/send
 
{
"user_ref": "user_123"
}

POST /v1/otp/verify

Verifies the entered code.

user_ref string required

Subscriber identifier

code string required

6-digit code from the user

POST /v1/otp/verify
 
{
"user_ref": "user_123",
"code": "482916"
}

OTP limits: max 5 verification attempts, 5-minute code TTL, 1 active code per user_ref.

Broadcast (mass delivery)

Send a message to all subscribers or a segment.

POST /v1/broadcast

Creates a broadcast job. Messages are sent through a queue.

text string required

Message text

permission string

Optional: filter by permission

tags array

Optional: filter by tags ["vip", "beta"]

POST /v1/broadcast
 
{
"text": "Version 2.0 available!",
"permission": "updates",
"tags": ["beta"]
}

GET /v1/broadcast/:job_id

Get broadcast status.

Response fields: status (pending/processing/completed/failed), total, sent, failed.

{
"job_id": "b7f3...",
"status": "completed",
"total": 2847,
"sent": 2835,
"failed": 12
}

Subscribers

Manage subscriber list and their tags.

GET /v1/subscribers

Project subscriber list. Supports pagination: ?page=1&per_page=50.

PUT /v1/subscribers/:id/tags

Update subscriber tags. Pass the full array of tags.

PUT /v1/subscribers/sub_abc/tags
 
{
"tags": ["vip", "beta"]
}

Templates

Templates let you reuse text with variables. Created in the dashboard.

Usage: pass template and vars instead of text in /v1/send.

Variables in templates use {{name}} syntax. Example: "Order {{order_id}} delivered".

Example

POST /v1/send
 
{
"user_ref": "user_123",
"template": "order_delivered",
"vars": {
"order_id": "1042",
"customer": "John"
}
}

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"}

Example with media and buttons

POST /v1/send
 
{
"user_ref": "user_123",
"text": "Your order is ready",
"media": {
"type": "photo",
"url": "https://example.com/photo.jpg"
},
"buttons": [[
{"text": "Details", "url": "https://..."},
{"text": "Cancel", "callback_data": "cancel_123"}
]]
}

Webhooks

Zapnoty sends HTTP POST to your URL when events occur.

Events: subscriber.new, subscriber.stop, message.delivered, message.failed, otp.verified.

Signature: X-Zapnoty-Signature header contains HMAC-SHA256 of request body with your webhook secret.

Signature verification:

// Node.js
const crypto = require('crypto');
 
const signature = req.headers['x-zapnoty-signature'];
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(req.body))
.digest('hex');
 
if (signature !== expected) // отклонить запрос

Payload format:

{
"event": "subscriber.new",
"timestamp": "2026-03-05T12:00:00Z",
"data": {
"subscriber_id": "sub_abc",
"user_ref": "user_123",
"channel": "telegram"
}
}

Limits

Rate limits and field size restrictions.

Rate limit: 300 requests/min per project. Exceeding it returns 429 Too Many Requests.

Message text: up to 4,096 characters.

Buttons: up to 3 rows, up to 3 buttons per row.

Media: up to 20 MB (photo), 50 MB (video/document).

Broadcast: up to 100,000 subscribers per broadcast.

OTP: 5-min TTL, max 5 attempts, 1 active code per user_ref.

Tags: up to 20 tags per subscriber, tag length up to 64 characters.

Error codes

API returns standard HTTP codes with JSON error body.

Error format:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "text is required"
}
}

400 — Invalid request (missing required fields, wrong format)

401 — Invalid or missing API key

403 — No access to resource

404 — Subscriber or resource not found

409 — Conflict (e.g., OTP already sent)

422 — Validation error (text too long, invalid URL)

429 — Rate limit exceeded

500 — Internal server error

Playground

Build an API request and copy the ready curl command.

Request body
{
  "user_ref": "user_123",
  "text": "Hello from Zapnoty!"
}
curl command
curl -X POST https://api.zapnoty.com/v1/send \
  -H "Authorization: Bearer zn_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "user_ref": "user_123",
  "text": "Hello from Zapnoty!"
}'