Documentation
Everything you need to send notifications via the Notify Router API.
Getting Started
Notify Router turns a single API call into delivery across one or more platforms. You own Platforms (Telegram, Slack, Email, SMS, WhatsApp, Webhook), group them into Channels with a per-link priority, and authenticate every send with an API Key. Routing is decided at send time by two fields: channel_ids (which channels to hit) and mode (broadcast or route).
The API Key only authenticates the caller — it does not own platforms or carry routing config. Before you can send, you need an account with an active package, at least one active Platform, and a Channel that links to it.
Step 1 — Connect a Platform
A Platform is one delivery destination (a Telegram bot, a Slack webhook, an email sender, and so on). Credentials are stored encrypted at rest.
- Open the Platforms page in your dashboard.
- Add a connection and pick its type (Telegram, Slack, Email, SMS, WhatsApp, or Webhook).
- Enter its credentials (bot token, webhook URL, SMTP settings, …).
- Make sure the connection is marked Active — inactive platforms are skipped.
Step 2 — Create a Channel & attach Platforms
A Channel is a routing group. You attach one or more Platforms to a Channel, and each link carries a priority used by route-mode failover (lower priority number = tried first).
- Open the Channels page and create a channel (e.g. “Alerts”).
- Attach one or more platforms to it.
- Set each platform link’s priority for route-mode fallback.
- Copy the channel’s ID — you pass it as channel_ids when sending.
Step 3 — Create an API Key
The API Key is the credential your client sends in the x-api-key header. Treat it like a password.
- Open the API Keys page and create a key.
- Copy the key value immediately — it is shown only once.
- Optional: set Default Channels on the key so sends can omit channel_ids entirely.
- Keep the key on your server / backend; never expose it in public client-side code.
Step 4 — Send your first notification
Make a POST request to https://notifyroute.com/api/v1/ingest with your key in the x-api-key header and a JSON body.
curl -X POST https://notifyroute.com/api/v1/ingest \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"channel_ids": ["YOUR_CHANNEL_ID"],
"mode": "broadcast",
"payload": { "title": "Hello", "body": "First notification" }
}'const res = await fetch("https://notifyroute.com/api/v1/ingest", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "YOUR_API_KEY",
},
body: JSON.stringify({
channel_ids: ["YOUR_CHANNEL_ID"],
mode: "broadcast",
payload: { title: "Hello", body: "First notification" },
}),
});
const data = await res.json(); // { data: { message_id } }A successful call returns 202 Accepted with a message_id; delivery then happens asynchronously. You can watch the result on the Messages page or via GET /api/v1/ingest/logs.
Easy path: if you set Default Channels on the API key, you can omit channel_ids and just send a payload.
// With default channels set on the API key, omit channel_ids:
{
"mode": "broadcast",
"payload": { "title": "Hello", "body": "World" }
}API Reference
POST /api/v1/ingest — enqueue a notification. Header: x-api-key (required). Body fields:
| Field | Type | Required | Notes |
|---|---|---|---|
| channel_ids | string[] (UUID) | Yes* | *Optional if the API key has Default Channels set. |
| mode | string | No | 'broadcast' (default) or 'route'. route allows at most 1 channel. |
| payload.title | string | No | Notification title. |
| payload.body | string | No | Notification body. Provide title/body, or variables, or template_id. |
| template_id | string (UUID) | No | Use a saved template (optional override). |
| variables | object | No | Key/value map injected into the template. |
| recipient.email | string | No | Required for Email platforms. |
| recipient.phone | string | No | Required for SMS platforms. |
HTTP/1.1 202 Accepted
{ "data": { "message_id": "8e0c…" } }GET /api/v1/ingest/logs — paginated delivery history for the authenticated key. Query params: page, per_page.
Errors return a non-2xx status with { "error": { "code", "error_code", "message", "meta" } }. Common cases:
| HTTP | error_code | Meaning |
|---|---|---|
| 400 | — | Invalid mode, missing channel_ids, route mode with >1 channel, or no content. |
| 401 | — | Missing or invalid x-api-key. |
| 403 | — | Channel not owned by your account. |
| 403 | subscription_expired | Your package has expired — renew it. |
| 403 | api_key_expired | The API key has passed its expiry date. |
| 429 | — | Daily or monthly quota exceeded. |
Quotas come from your package: a daily and a monthly limit. A value of -1 means unlimited. Exceeding either returns 429 Too Many Requests.
Guides
Broadcast vs Route — broadcast fans out to every platform across all selected channels, automatically de-duplicating platforms that appear in more than one channel. Route targets exactly one channel and fails over platform-by-platform in priority order until one succeeds.
{
"channel_ids": ["ONE_CHANNEL_ID"],
"mode": "route",
"payload": { "title": "Order #1024", "body": "New paid order" }
}Templates & variables — create a template in the dashboard, then reference it by template_id and pass a variables map to fill placeholders.
{
"channel_ids": ["YOUR_CHANNEL_ID"],
"mode": "broadcast",
"template_id": "YOUR_TEMPLATE_ID",
"variables": { "name": "Lan", "amount": "299.000đ" }
}Recipient — Email and SMS platforms need a destination address. Pass recipient.email and/or recipient.phone.
{
"channel_ids": ["YOUR_CHANNEL_ID"],
"mode": "broadcast",
"payload": { "title": "Receipt", "body": "Thanks for your order" },
"recipient": { "email": "lan@example.com", "phone": "+84901234567" }
}LadiPage / form integration — in your form’s API/Webhook config, set the request to POST to https://notifyroute.com/api/v1/ingest, add the header x-api-key: YOUR_API_KEY, and map form fields into the JSON body (e.g. payload.body). If your form builder cannot send custom headers, set Default Channels on the API key and route the form through a webhook relay (Make, n8n, or a small serverless function) that adds the header.
Troubleshooting — a message stuck as failed usually means no active platform is attached to the channel. “400 channel_ids is required” means you must pass channel_ids or set Default Channels on the key. 429 means you hit your quota. “403 subscription_expired” means you must renew your package.