Two things worth knowing
A 202 means the message is durably stored and queued. Only the status field claims WhatsApp delivered anything, and queued, submitted, sent, delivered and read stay distinct.
Send Idempotency-Key on every send. A replay returns the original response; a concurrent duplicate is told to retry rather than allowed to race.
This page is generated from the same OpenAPI document the API serves at /v1/openapi.json, and every request shape below is the schema the endpoint actually validates with.
Messages
Sending, delivery state and export.
/v1/connections/{connection}/presence
Show typing in a chat
API key · messages:write
Shows "typing…" or "recording…" to a person or group, or stops (paused). WhatsApp clears it by itself after a short while or when a message arrives. Nothing is stored.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Body
| Field | Type | Notes | |
|---|---|---|---|
to | one of several shapes | required | |
state | typing | recording | paused | required |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/presence \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+447700900123",
"state": "typing"
}'Responses
| Status | Meaning |
|---|---|
204 | Shown. |
404 | No such connection. |
409 | The connection is not ready. |
422 | The connection's engine cannot show typing. |
/v1/messages
List messages
API key · messages:read
Newest first, cursor paginated. Offsets are not offered: they skip and duplicate.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
limit | query | integer | optional | 1 to 100; default 25 |
cursor | query | string | optional | length 0–200 |
connection | query | string | optional | length 1–40 |
status | query | scheduled | accepted | queued | submitting | submitted | sent | delivered | read | failed | canceled | optional | |
direction | query | inbound | outbound | optional | |
q | query | string | optional | length 1–120 |
Request
curl -X GET https://www.chatrail.dev/v1/messages \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | A page of messages and a next_cursor. |
/v1/messages/export
Export delivery metadata as CSV
API key · messages:read
Streamed row by row. Message bodies are not included — this is delivery metadata, not content.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | query | string | optional | length 1–40 |
status | query | string | optional | length 1–20 |
direction | query | inbound | outbound | optional | |
days | query | integer | optional | 1 to 365; default 30 |
Request
curl -X GET https://www.chatrail.dev/v1/messages/export \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | A CSV stream. |
/v1/messages/text
Send a message
API key · messages:write
Accepts a message for delivery and returns 202. 202 means accepted, never delivered — the message is durably stored and queued, and only the status field ever claims delivery. Send media or location for an attachment, send_at to defer it. Supply Idempotency-Key so a retry cannot send twice. A first message to someone the number has never talked to is checked against WhatsApp and paced: 10 an hour in a number's first week, 30 after, at least 20 seconds apart. Over the pace it stays queued and goes out later; nothing is refused. A message WhatsApp accepts but never confirms sending within 24 hours is failed with not_confirmed.
Body
| Field | Type | Notes | |
|---|---|---|---|
connection | string | required | length 1–40 |
to | one of several shapes | required | |
body | string | optional | length 0–4096 |
media | object | optional | |
location | object | optional | |
contact | object | optional | |
poll | object | optional | |
reply_to | string (uuid) | optional | uuid format |
mentions | array of string | optional | |
link_preview | boolean | optional | |
context | object | optional | |
metadata | object | optional | |
pause_ai | boolean | optional | |
context_sensitivity | normal | sensitive | restricted | optional | |
send_at | string (date-time) | optional | date-time format |
timezone | string | optional | length 1–64 |
Request
curl -X POST https://www.chatrail.dev/v1/messages/text \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"connection": "main",
"to": "+15551234567",
"body": "Order CR-2048 has shipped.",
"context": {
"order_id": "CR-2048",
"status": "dispatched"
}
}'Responses
| Status | Meaning |
|---|---|
202 | Accepted and queued. |
400 | The body, recipient or attachment was rejected. |
402 | The plan does not include this. The message names what would allow it. |
409 | The connection is not ready, or an identical Idempotency-Key is in flight. |
422 | The Idempotency-Key was reused with a different body. |
429 | Quota or rate limit. |
/v1/messages/{id}
Get a message
API key · messages:read
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Message id. |
Request
curl -X GET https://www.chatrail.dev/v1/messages/:id \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The message and its delivery timeline. |
404 | No such message. |
/v1/messages/{id}
Edit a message
API key · messages:write
Replaces the text of a text message this number sent, within WhatsApp's 15-minute window, and/or its context. Captions cannot be changed after sending. The first text is kept and audited. Editing the text does not change the context the AI answers from: send context too when the correction matters to it (a new gate, a new time). A context-only edit has no window and does not reach WhatsApp.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Message id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
body | string | optional | length 0–4096 |
context | object | optional | |
context_sensitivity | normal | sensitive | restricted | optional |
Request
curl -X PATCH https://www.chatrail.dev/v1/messages/:id \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "Corrected: viewing is at 3pm, not 2pm.",
"context": {
"property": {
"address": "14 Mill Lane",
"viewing": "15:00"
}
}
}'Responses
| Status | Meaning |
|---|---|
200 | The message, with edited_at set. |
404 | No such message. |
409 | Not editable: received, not a text message, deleted, or past the window. |
/v1/messages/{id}/cancel
Cancel a message
API key · messages:write
Only while it has not reached the provider. Once submitted the message exists on somebody's phone and cancelling would be a lie.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Message id. |
Request
curl -X POST https://www.chatrail.dev/v1/messages/:id/cancel \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Cancelled. |
404 | No such message. |
409 | Already handed to the provider. |
/v1/messages/{id}/correlation
Correct which message a reply answers
API key · messages:write
Re-points an inbound reply at the outbound message it actually answers, or unlinks it with null. The engine's original choice is preserved, so corrections measure the correlation rules rather than quietly overwriting them. The previously linked alert reopens for correlation; the newly linked one is marked answered.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Inbound message id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
reply_to_message_id | one of several shapes | required |
Request
curl -X POST https://www.chatrail.dev/v1/messages/:id/correlation \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reply_to_message_id": "019bf4a1-0000-7000-8000-000000000000"
}'Responses
| Status | Meaning |
|---|---|
200 | The corrected link, and what it overruled. |
400 | The target is not an outbound message in the same conversation. |
404 | No such message. |
409 | Only an inbound message has a correlation to correct. |
/v1/messages/{id}/media
Download an inbound attachment
API key · messages:read
Streams the file from the transport through ChatRail. Inbound only: an outbound attachment's URL is one the caller supplied and already has. The response carries the stored content type, never the transport's, and is always served as an attachment. Answers 404 when the message has no attachment, when the transport reported a file it does not serve, or when it no longer holds it.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Inbound message id. |
Request
curl -X GET https://www.chatrail.dev/v1/messages/:id/media \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The file. |
404 | No downloadable media on this message. |
503 | This deployment cannot retrieve media from the transport. |
/v1/messages/{id}/reactions
React to a message
API key · messages:write
Sets this number's reaction on a message it sent or received; an empty emoji removes it. Works on any message that has reached WhatsApp.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Message id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
emoji | string | required | length 0–16 |
Request
curl -X POST https://www.chatrail.dev/v1/messages/:id/reactions \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emoji": "\ud83d\udc4d"
}'Responses
| Status | Meaning |
|---|---|
200 | The message, with reaction set. |
404 | No such message. |
409 | Not possible for this message, or WhatsApp refused. |
422 | The connection's engine does not support reactions. |
/v1/messages/{id}/read
Mark a message read
API key · messages:write
Shows blue ticks to the sender of an inbound message. Idempotent: a second call sends nothing.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Message id. |
Request
curl -X POST https://www.chatrail.dev/v1/messages/:id/read \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The message, with seen_at set. |
404 | No such message. |
409 | Only a received message can be marked read. |
/v1/messages/{id}/reschedule
Move a scheduled message
API key · messages:write
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Message id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
send_at | string (date-time) | required | date-time format |
timezone | string | optional | length 1–64 |
Request
curl -X POST https://www.chatrail.dev/v1/messages/:id/reschedule \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"send_at": "2026-10-01T09:00:00Z",
"timezone": "Europe/London"
}'Responses
| Status | Meaning |
|---|---|
200 | Rescheduled. |
409 | No longer scheduled. |
/v1/messages/{id}/revoke
Delete a message for everyone
API key · messages:write
Removes a message this number sent from every phone in the chat, within WhatsApp's window of about two and a half days. The record stays in ChatRail, marked deleted, and the deletion is audited.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Message id. |
Request
curl -X POST https://www.chatrail.dev/v1/messages/:id/revoke \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The message, with deleted_at set. |
404 | No such message. |
409 | Not deletable: received, already deleted, or past the window. |
/v1/numbers/check
Check whether a number is on WhatsApp
API key · messages:read
Answers checked: false when the engine cannot say. Treat that as "send anyway": refusing a message because a check failed turns a missing provider feature into a delivery failure. chat_id is WhatsApp's own id for the chat when it gives one. An answer from the last 24 hours is reused (cached: true) without asking WhatsApp again. Lookups that do reach WhatsApp go through your connected number and are limited to 10 a minute per workspace, and 30 a day on Sandbox.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
phone | query | string | required | length 5–40 |
connection | query | string | required | length 1–40 |
Request
curl -X GET https://www.chatrail.dev/v1/numbers/check \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The answer, or checked: false. |
404 | No such connection. |
429 | No lookup budget left; Retry-After says how long to wait. |
/v1/numbers/check
Check up to 10 numbers
API key · messages:read
The same check for several numbers. Each gets its own line in results, in the order sent: a number that cannot be parsed has reason: invalid_number, and numbers a spent budget could not reach have reason: minute_limit or daily_limit instead of failing the request. Repeats in one request are looked up once.
Body
| Field | Type | Notes | |
|---|---|---|---|
connection | string | required | length 1–40 |
phones | array of string | required |
Request
curl -X POST https://www.chatrail.dev/v1/numbers/check \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connection": "main",
"phones": [
"+923001234567",
"+2348031234567"
]
}'Responses
| Status | Meaning |
|---|---|
200 | One result per number. |
400 | No numbers, or more than 10. |
404 | No such connection. |
Connections
WhatsApp numbers and their pairing lifecycle.
/v1/connections
List connections
API key · connections:read
Request
curl -X GET https://www.chatrail.dev/v1/connections \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Every live connection in the workspace. |
/v1/connections
Create a connection
API key · connections:write
Returns callback_token exactly once. It cannot be retrieved again; store it when you receive it. On the Team plan, add proxy to link this number through its own HTTP proxy instead of ChatRail's address: server is an IP address and port (not a hostname, and a public address), with an optional username and password that are stored encrypted and never returned. Use a fixed address, ideally in the same country as the number.
Body
| Field | Type | Notes | |
|---|---|---|---|
slug | string | required | pattern ^[a-z0-9][a-z0-9-]{0,38}[a-z0-9]$ |
name | string | optional | length 1–80 |
proxy | object | optional |
Request
curl -X POST https://www.chatrail.dev/v1/connections \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "operations",
"name": "Operations line"
}'Responses
| Status | Meaning |
|---|---|
201 | Created, with the one-time callback token. |
400 | The request is not valid, such as a proxy that is a hostname or a private address. |
402 | The plan has no room for another number, or does not include proxies. |
409 | That slug is taken. |
422 | The WhatsApp engine cannot use a proxy. |
/v1/connections/{connection}
Get a connection
API key · connections:read
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Request
curl -X GET https://www.chatrail.dev/v1/connections/:connection \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The connection and its state. |
404 | No such connection. |
/v1/connections/{connection}
Change connection settings
API key · connections:write
Send only the settings you want to change. reject_calls declines incoming calls on this number automatically. It is off by default, because the caller sees the call declined. Each call is still announced as call.received, with rejected saying whether it was declined. proxy routes this number through its own HTTP proxy (Team plan), or removes it with null (any plan). A proxy is server (an IP address and port, not a hostname, and a public address) with an optional username and password. It is applied when the number is paired, so it can only be changed before the number is linked, or after WhatsApp has logged it out. The response shows the address and has_credentials; the username and password are never returned.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Body
| Field | Type | Notes | |
|---|---|---|---|
reject_calls | boolean | optional | |
proxy | one of several shapes | optional |
Request
curl -X PATCH https://www.chatrail.dev/v1/connections/:connection \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reject_calls": true
}'Responses
| Status | Meaning |
|---|---|
200 | The updated connection. |
400 | A setting is not valid, such as a proxy that is a hostname or a private address. |
402 | The plan does not include proxies. |
404 | No such connection. |
409 | The proxy cannot be changed: the number is linked. |
422 | The number's WhatsApp engine cannot use a proxy. |
/v1/connections/{connection}
Delete a connection
API key · connections:write
Soft deletes it and queues removal of its data.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Request
curl -X DELETE https://www.chatrail.dev/v1/connections/:connection \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
204 | Deleted. |
404 | No such connection. |
/v1/connections/{connection}/groups
List WhatsApp groups
API key · connections:read
Returns a bounded, name-sorted page for a group picker. Group ids use the same from.group representation delivered in message webhooks. Participant identities are never returned.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
limit | query | integer | optional | 1 to 200; default 100 |
offset | query | integer | optional | 0 to 100000; default 0 |
Request
curl -X GET https://www.chatrail.dev/v1/connections/:connection/groups \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | A page of groups. |
404 | No such connection. |
502 | The transport returned an unreadable response. |
503 | The transport is unavailable. |
/v1/connections/{connection}/pair
Begin pairing
API key · connections:write
Issues a short-lived, single-use grant. By default it holds a QR; with {"method": "code", "phone": "+447700900123"} it holds a pairing code instead, which the person enters under Linked devices → Link with phone number (encoding pairing-code). Either is fetched once with the grant id: each is a pairing credential, so whoever uses it links *their* device to the number.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Body
| Field | Type | Notes | |
|---|---|---|---|
method | qr | code | optional | default "qr" |
phone | string | optional | length 5–40 |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/pair \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"method": "code",
"phone": "+447700900123"
}'Responses
| Status | Meaning |
|---|---|
200 | A grant id and its expiry. |
409 | The connection cannot be paired from its current state. |
503 | No transport is configured on this deployment. |
/v1/connections/{connection}/qr/{grantId}
Redeem a QR grant
API key · connections:write
Single use. A redeemed or expired grant is gone; ask for another.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
grantId | path | string | required | Grant id. |
Request
curl -X GET https://www.chatrail.dev/v1/connections/:connection/qr/:grantId \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The pairing payload. |
404 | Unknown, used or expired. |
/v1/connections/{connection}/resume
Resume a suspended connection
API key · connections:write
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/resume \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Resumed. |
409 | Not suspended. |
/v1/connections/{connection}/suspend
Suspend a connection
API key · connections:write
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/suspend \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Suspended. |
409 | Not in a suspendable state. |
Groups
Creating and running WhatsApp groups from a connected number.
/v1/connections/{connection}/groups
Create a group
API key · connections:write
Creates a WhatsApp group with this number as its owner. Everyone added counts against the member-add guardrail: at most 20 people per connection per hour, because adding people who did not ask to be added is how numbers get reported. Paid plans.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Body
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | length 1–100 |
participants | array of string | required |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/groups \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Riverside viewings",
"participants": [
"+447700900123",
"+447700900456"
]
}'Responses
| Status | Meaning |
|---|---|
201 | The new group, with its members. |
402 | Not on this plan. |
429 | Over the member-add guardrail for this hour. |
/v1/connections/{connection}/groups/{group}
Get a group
API key · connections:read
Name, description, settings and members with their roles.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
group | path | string | required | Group id. |
Request
curl -X GET https://www.chatrail.dev/v1/connections/:connection/groups/:group \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The group. |
404 | No such group for this number. |
/v1/connections/{connection}/groups/{group}
Update a group
API key · connections:write
Change the name or description, or who may send messages and edit group info. This number must be an admin of the group. Paid plans.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
group | path | string | required | Group id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
name | string | optional | length 1–100 |
description | string | optional | length 0–2048 |
messages_admins_only | boolean | optional | |
info_admins_only | boolean | optional |
Request
curl -X PATCH https://www.chatrail.dev/v1/connections/:connection/groups/:group \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages_admins_only": true
}'Responses
| Status | Meaning |
|---|---|
200 | The updated group. |
409 | WhatsApp refused; not an admin? |
/v1/connections/{connection}/groups/{group}/invite
Get a group invite link
API key · connections:read
The chat.whatsapp.com link for joining the group.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
group | path | string | required | Group id. |
Request
curl -X GET https://www.chatrail.dev/v1/connections/:connection/groups/:group/invite \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The link. |
/v1/connections/{connection}/groups/{group}/invite/revoke
Revoke a group invite link
API key · connections:write
Invalidates the current link and returns a new one.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
group | path | string | required | Group id. |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/groups/:group/invite/revoke \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The new link. |
/v1/connections/{connection}/groups/{group}/leave
Leave a group
API key · connections:write
This number leaves the group.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
group | path | string | required | Group id. |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/groups/:group/leave \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
204 | Left. |
/v1/connections/{connection}/groups/{group}/participants
Change group members
API key · connections:write
add, remove, promote to admin or demote. Adding counts against the member-add guardrail (20 per connection per hour). Paid plans.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
group | path | string | required | Group id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
action | add | remove | promote | demote | required | |
participants | array of string | required |
Request
curl -X POST https://www.chatrail.dev/v1/connections/:connection/groups/:group/participants \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "add",
"participants": [
"+447700900789"
]
}'Responses
| Status | Meaning |
|---|---|
200 | The group after the change. |
429 | Over the member-add guardrail for this hour. |
Schedules
Recurring sends.
/v1/schedules
List schedules
API key · schedules:read
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
status | query | active | paused | completed | canceled | optional |
Request
curl -X GET https://www.chatrail.dev/v1/schedules \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Every schedule in the workspace. |
/v1/schedules
Create a recurring schedule
API key · schedules:write
Daily, weekly or monthly, resolved in the given IANA timezone. A schedule that fell behind fires once and drops the backlog rather than delivering days of stale messages.
Body
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | length 1–80 |
timezone | string | required | length 1–64 |
recurrence | one of several shapes | required | |
message | object | required | |
starts_at | string (date-time) | optional | date-time format |
ends_at | string (date-time) | optional | date-time format |
max_occurrences | integer | optional | 1 to 10000 |
Request
curl -X POST https://www.chatrail.dev/v1/schedules \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Friday digest",
"timezone": "Europe/London",
"recurrence": {
"type": "weekly",
"hour": 9,
"minute": 0,
"days_of_week": [
5
]
},
"message": {
"connection": "main",
"to": "+15551234567",
"body": "Your weekly digest is ready."
}
}'Responses
| Status | Meaning |
|---|---|
201 | Created, with the resolved next_run_at. |
400 | Unknown timezone, or a recurrence that would never fire. |
402 | The plan does not include scheduling. |
/v1/schedules/{id}
Get a schedule
API key · schedules:read
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Schedule id. |
Request
curl -X GET https://www.chatrail.dev/v1/schedules/:id \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The schedule. |
404 | No such schedule. |
/v1/schedules/{id}/cancel
Cancel a schedule
API key · schedules:write
Permanent. A cancelled schedule cannot be restarted.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Schedule id. |
Request
curl -X POST https://www.chatrail.dev/v1/schedules/:id/cancel \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The updated schedule. |
409 | Not in a state that allows it. |
/v1/schedules/{id}/pause
Pause a schedule
API key · schedules:write
Keeps the schedule and stops it firing.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Schedule id. |
Request
curl -X POST https://www.chatrail.dev/v1/schedules/:id/pause \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The updated schedule. |
409 | Not in a state that allows it. |
/v1/schedules/{id}/resume
Resume a schedule
API key · schedules:write
The next run is computed from now. A schedule paused for a month does not fire the month it missed.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Schedule id. |
Request
curl -X POST https://www.chatrail.dev/v1/schedules/:id/resume \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The updated schedule. |
409 | Not in a state that allows it. |
Webhooks
Where inbound events and delivery updates are sent.
/v1/webhook-endpoints
List webhook endpoints
API key · webhooks:read
Request
curl -X GET https://www.chatrail.dev/v1/webhook-endpoints \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Endpoints, without their signing secrets. |
/v1/webhook-endpoints
Register a webhook endpoint
API key · webhooks:write
Returns the signing secret exactly once. Every delivery is signed with HMAC-SHA256 over the timestamp, a period, and the raw request body; verify it and reject stale timestamps. The URL is validated at registration and the connection pinned at delivery, so an endpoint pointing at a private address is refused. Works with an API key holding webhooks:write, so a deploy script can register its own endpoint; every change is audited.
Body
| Field | Type | Notes | |
|---|---|---|---|
url | string (uri) | required | length 0–2048 |
description | string | optional | length 0–200 |
events | array of string | required | |
connection | string | optional | length 1–40 |
Request
curl -X POST https://www.chatrail.dev/v1/webhook-endpoints \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/chatrail",
"events": [
"message.received",
"message.delivered",
"message.failed"
],
"description": "Order updates"
}'Responses
| Status | Meaning |
|---|---|
201 | Created, with the one-time signing secret. |
400 | The URL is not publicly reachable over https. |
402 | Scoping to one connection is not on this plan. |
/v1/webhook-endpoints/{id}
Delete a webhook endpoint
API key · webhooks:write
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Endpoint id. |
Request
curl -X DELETE https://www.chatrail.dev/v1/webhook-endpoints/:id \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
204 | Deleted. |
404 | No such endpoint. |
/v1/webhook-endpoints/{id}/enable
Re-enable a webhook endpoint
API key · webhooks:write
Turns back on an endpoint that was disabled after 50 failed deliveries in a row. The failure count restarts at zero and the signing secret is unchanged. Events skipped while it was disabled are not re-sent.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Endpoint id. |
Request
curl -X POST https://www.chatrail.dev/v1/webhook-endpoints/:id/enable \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The endpoint, now active. |
404 | No such endpoint. |
Opt-outs
People who asked not to be messaged. Sends to them are refused.
/v1/opt-outs
List people who must not be messaged
API key · messages:read
Numbers on the workspace's opt-out list, oldest first. A number gets here two ways: the person wrote a stop word such as "STOP" in a direct chat (source: inbound_keyword), or you added it (source: api). Sends to a number on the list are refused with RECIPIENT_OPTED_OUT, and so are messages already queued or scheduled for it.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
limit | query | integer | optional | 1 to 500; default 100 |
cursor | query | string (uuid) | optional | uuid format |
Request
curl -X GET https://www.chatrail.dev/v1/opt-outs \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | A page of opt-outs, with next_cursor when there is more. |
/v1/opt-outs
Add a number to the opt-out list
API key · messages:write
For someone who has asked you, outside WhatsApp, to stop. Adding a number that is already on the list succeeds and changes nothing. Sending to it is refused from then on, including anything already queued or scheduled.
Body
| Field | Type | Notes | |
|---|---|---|---|
phone | string | required | length 1–40 |
Request
curl -X POST https://www.chatrail.dev/v1/opt-outs \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+447700900123"
}'Responses
| Status | Meaning |
|---|---|
200 | Already on the list. |
201 | Added. |
400 | Not a valid phone number. |
/v1/opt-outs/{phone}
Remove a number from the opt-out list
API key · messages:write
Only for a person who has asked to hear from you again. If they write a stop word again they are opted out again.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
phone | path | string | required | The number in international form, with or without the leading +. |
Request
curl -X DELETE https://www.chatrail.dev/v1/opt-outs/:phone \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
204 | Removed. |
400 | Not a valid phone number. |
404 | That number is not on the list. |
AI
Optional, per connection, off by default.
/v1/ai/credentials
List credentials
API key · ai:read
Fingerprints and metadata only; the key itself is never returned.
Request
curl -X GET https://www.chatrail.dev/v1/ai/credentials \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Credential metadata. |
/v1/ai/credentials
Store a model provider credential
API key · ai:write
Encrypted at rest and never returned. AI is off by default and enabled per connection.
Body
| Field | Type | Notes | |
|---|---|---|---|
provider | openai_compatible | anthropic | required | |
label | string | required | length 1–80 |
api_key | string | required | length 8–8192 |
base_url | string (uri) | optional |
Request
curl -X POST https://www.chatrail.dev/v1/ai/credentials \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "anthropic",
"label": "Production key",
"api_key": "sk-ant-replace-with-your-own-key"
}'Responses
| Status | Meaning |
|---|---|
201 | Stored. |
402 | AI is not on this plan. |
/v1/ai/credentials/{id}
Delete a credential
API key · ai:write
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Credential id. |
Request
curl -X DELETE https://www.chatrail.dev/v1/ai/credentials/:id \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
204 | Deleted. |
404 | No such credential. |
/v1/ai/credentials/{id}/test
Test a credential
API key · ai:write
Makes one small call to the model provider with this credential and the given model, so a wrong key or model name shows up before a customer is waiting on a reply.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Credential id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
model | string | required | length 1–120 |
Request
curl -X POST https://www.chatrail.dev/v1/ai/credentials/:id/test \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-6-luna"
}'Responses
| Status | Meaning |
|---|---|
200 | The provider answered. |
404 | No such credential. |
502 | The provider refused or failed; the body says why. |
/v1/ai/policies/{connection}
Get the AI policy for a connection
API key · ai:read
Absent policy means AI is off for that connection, which is the default.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Request
curl -X GET https://www.chatrail.dev/v1/ai/policies/:connection \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The policy. |
404 | No policy is set. |
/v1/ai/policies/{connection}
Set the AI policy for a connection
API key · ai:write
Draft or auto mode, with token, rate and monthly cost ceilings. Absent policy means AI is off for that connection.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
connection | path | string | required | Connection slug. |
Body
| Field | Type | Notes | |
|---|---|---|---|
credential_source | byok | managed | optional | default "byok" |
credential_id | one of several shapes | optional | |
enabled | boolean | required | |
mode | draft | auto | required | |
model | string | optional | length 1–120 |
system_instruction | string | required | length 1–4000 |
max_context_bytes | integer | optional | 256 to 32768; default 8192 |
max_output_tokens | integer | optional | 32 to 2000; default 300 |
include_history | boolean | optional | default false |
humanize | boolean | optional | default true |
human_takeover_minutes | integer | optional | 0 to 43200; default 1440 |
reply_delay_seconds | integer | optional | 0 to 120; default 5 |
group_replies | off | addressed | all | optional | default "addressed" |
monthly_cost_limit_micros | one of several shapes | optional | default null |
input_cost_micros_per_million | integer | optional | 0 to 9007199254740991; default 0 |
output_cost_micros_per_million | integer | optional | 0 to 9007199254740991; default 0 |
Request
curl -X PUT https://www.chatrail.dev/v1/ai/policies/:connection \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"credential_id": "019bf4a1-0000-7000-8000-000000000000",
"enabled": true,
"mode": "draft",
"model": "claude-sonnet-4-5",
"system_instruction": "Answer only from the attached context. If the answer is not there, say so."
}'Responses
| Status | Meaning |
|---|---|
200 | The policy. |
402 | AI is not on this plan. |
/v1/ai/quality
Summarise what happened to AI drafts
API key · ai:read
Counts only, never text: drafts sent unchanged, sent after editing, or dismissed and why; runs that ran with no business context at all; and correlation corrections grouped by the rule that originally fired. Accepts days (default 30).
Request
curl -X GET https://www.chatrail.dev/v1/ai/quality \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The summary. |
/v1/ai/runs
List AI runs
API key · ai:read
Usage, cost, outcome and the proposed reply. The assembled prompt is represented by its hash and the list of context keys that went into it, never reproduced.
Request
curl -X GET https://www.chatrail.dev/v1/ai/runs \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Recent runs. |
/v1/ai/runs/{id}/dismiss
Dismiss an AI draft
API key · ai:write
Removes a pending draft from the human review queue without sending it. Supply a reason where you know it: the counts are what make draft quality measurable, and wrong_correlation in particular is the only signal that a reply was matched to the wrong alert.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | AI run id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
reason | wrong_context | wrong_correlation | inaccurate | tone | not_needed | needs_human | other | optional |
Request
curl -X POST https://www.chatrail.dev/v1/ai/runs/:id/dismiss \
-H "Authorization: Bearer $CHATRAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "wrong_context"
}'Responses
| Status | Meaning |
|---|---|
204 | Dismissed. |
409 | The draft cannot be dismissed. |
/v1/ai/runs/{id}/send
Send an AI draft
API key · ai:write
Accepts the pending draft through the normal durable message outbox.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | AI run id. |
Request
curl -X POST https://www.chatrail.dev/v1/ai/runs/:id/send \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The accepted message id. |
409 | The draft cannot be sent. |
Team
Membership and invitations. Dashboard sessions only.
/v1/members
List members and pending invitations
Dashboard session
Request
curl -X GET https://www.chatrail.dev/v1/membersResponses
| Status | Meaning |
|---|---|
200 | Members, invitations, and the roles this caller may assign. |
/v1/members/accept
Accept an invitation
Dashboard session
Requires a signed-in session whose address matches the invited one. Possessing the token is not enough — a forwarded invitation email must not be a way into a workspace.
Body
| Field | Type | Notes | |
|---|---|---|---|
token | string | required | length 20–200 |
Request
curl -X POST https://www.chatrail.dev/v1/members/accept \
-H "Content-Type: application/json" \
-d '{
"token": "the token from the invitation link"
}'Responses
| Status | Meaning |
|---|---|
200 | Joined. |
401 | Sign in as the invited address first. |
403 | The invitation was sent to a different address. |
404 | The invitation is not valid. |
/v1/members/invitations
Invite somebody
Dashboard session
Emails a single-use link bound to that address. The token is never returned in the response. Nobody may invite to a role above their own.
Body
| Field | Type | Notes | |
|---|---|---|---|
email | string (email) | required | length 0–254; email format |
role | owner | admin | developer | viewer | billing | required |
Request
curl -X POST https://www.chatrail.dev/v1/members/invitations \
-H "Content-Type: application/json" \
-d '{
"email": "colleague@company.com",
"role": "viewer"
}'Responses
| Status | Meaning |
|---|---|
201 | Invited. |
402 | Member management is not on this plan. |
403 | You cannot assign that role. |
409 | Already a member. |
/v1/members/invitations/{id}
Revoke an invitation
Dashboard session
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Invitation id. |
Request
curl -X DELETE https://www.chatrail.dev/v1/members/invitations/:idResponses
| Status | Meaning |
|---|---|
204 | Revoked. |
404 | No such invitation. |
/v1/members/invitations/{id}/resend
Resend an invitation
Dashboard session
Emails the invitation again with a fresh link. Dashboard only.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Invitation id. |
Request
curl -X POST https://www.chatrail.dev/v1/members/invitations/:id/resendResponses
| Status | Meaning |
|---|---|
200 | Resent. |
404 | No such invitation. |
/v1/members/{id}
Change a member's role
Dashboard session
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | User id. |
Body
| Field | Type | Notes | |
|---|---|---|---|
role | owner | admin | developer | viewer | billing | required |
Request
curl -X PATCH https://www.chatrail.dev/v1/members/:id \
-H "Content-Type: application/json" \
-d '{
"role": "developer"
}'Responses
| Status | Meaning |
|---|---|
200 | The updated member. |
403 | You cannot assign or act on that role. |
409 | A workspace must keep an owner. |
/v1/members/{id}
Remove a member
Dashboard session
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | User id. |
Request
curl -X DELETE https://www.chatrail.dev/v1/members/:idResponses
| Status | Meaning |
|---|---|
204 | Removed. |
409 | A workspace must keep an owner. |
Workspace
Identity, credentials, plan and usage.
/v1/api-keys
List API keys
Dashboard session
Request
curl -X GET https://www.chatrail.dev/v1/api-keysResponses
| Status | Meaning |
|---|---|
200 | Key metadata and prefixes, never the keys. |
/v1/api-keys
Issue an API key
Dashboard session
Returns the key exactly once. It is stored only as a hash and cannot be recovered. A key may never hold more than the person issuing it.
Body
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | length 1–80 |
scopes | array of messages:read | messages:write | connections:read | connections:write | webhooks:read | webhooks:write | schedules:read | schedules:write | ai:read | ai:write | mcp:read | mcp:write | usage:read | required | |
environment | live | test | optional | default "live" |
Request
curl -X POST https://www.chatrail.dev/v1/api-keys \
-H "Content-Type: application/json" \
-d '{
"name": "Order service",
"scopes": [
"messages:write",
"messages:read"
],
"environment": "live"
}'Responses
| Status | Meaning |
|---|---|
201 | The key, once. |
403 | Your role cannot grant those scopes. |
/v1/api-keys/{id}/revoke
Revoke an API key
Dashboard session
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Key id. |
Request
curl -X POST https://www.chatrail.dev/v1/api-keys/:id/revokeResponses
| Status | Meaning |
|---|---|
200 | Revoked, immediately. |
404 | No such key. |
/v1/api-keys/{id}/rotate
Rotate an API key
Dashboard session
Issues a replacement and revokes the old one.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
id | path | string | required | Key id. |
Request
curl -X POST https://www.chatrail.dev/v1/api-keys/:id/rotateResponses
| Status | Meaning |
|---|---|
200 | The new key, once. |
404 | No such key. |
/v1/usage
Usage counts
API key · usage:read
Counts only. A count of messages is not a message; no content is reachable here.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
days | query | integer | optional | 1 to 365 |
Request
curl -X GET https://www.chatrail.dev/v1/usage \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | Aggregated counts for the workspace. |
/v1/whoami
Confirm a credential
API key
Shows exactly what the presented credential can do. The first call to make.
Request
curl -X GET https://www.chatrail.dev/v1/whoami \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The principal, its scopes and its workspace. |
/v1/workspace
Get the current workspace
Dashboard session
Includes the plan and what has been used against it.
Request
curl -X GET https://www.chatrail.dev/v1/workspaceResponses
| Status | Meaning |
|---|---|
200 | The workspace, the caller role and the plan. |
/v1/workspace
Rename the workspace
Dashboard session
Changes the display name shown to members, on invitations and in emails. The slug does not change. Needs workspace:update.
Body
| Field | Type | Notes | |
|---|---|---|---|
name | string | required |
Request
curl -X PATCH https://www.chatrail.dev/v1/workspace \
-H "Content-Type: application/json" \
-d '{
"name": "Kettle & Co"
}'Responses
| Status | Meaning |
|---|---|
200 | The workspace id, slug and new name. |
400 | The name is empty or longer than 80 characters. |
Billing
Checkout, the customer portal and Creem webhooks.
/v1/billing
Get the billing account
Dashboard session
What Creem says about this workspace: customer, subscription, status and the current period. Distinct from the plan on /v1/workspace, which is what the workspace is actually entitled to and is never blocked on this being reachable.
Request
curl -X GET https://www.chatrail.dev/v1/billingResponses
| Status | Meaning |
|---|---|
200 | The billing account, or the free-tier default if there is none yet. |
/v1/billing/checkout
Start a checkout
Dashboard session
Returns a Creem-hosted checkout URL for the chosen paid plan. When the workspace has a paired number from a regional-pricing country, the checkout opens with that discount.
Body
| Field | Type | Notes | |
|---|---|---|---|
plan | core | core_ai | team | required | |
return_path | string | optional | length 0–200; default "/settings/billing" |
Request
curl -X POST https://www.chatrail.dev/v1/billing/checkout \
-H "Content-Type: application/json" \
-d '{
"plan": "core_ai",
"return_path": "/settings/billing"
}'Responses
| Status | Meaning |
|---|---|
201 | The checkout id and URL to redirect the browser to. |
404 | Billing is not configured for this workspace. |
/v1/billing/crypto
Crypto payment options
Dashboard session
Whether crypto checkout is available, the accepted coins, the prepaid price of each plan for 3 and 12 months (12 months costs 10), and this workspace's prepaid period and newest unpaid order, if any. A crypto purchase is a prepaid block of months, not a subscription: a wallet cannot be charged again automatically. With a regional price, price_usd is the reduced price, list_price_usd the full one, and regional names the discount.
Request
curl -X GET https://www.chatrail.dev/v1/billing/cryptoResponses
| Status | Meaning |
|---|---|
200 | Options and status. available: false when crypto is not configured. |
/v1/billing/crypto/checkout
Start a crypto checkout
Dashboard session
Creates a NOWPayments invoice for a prepaid period and returns its URL. The plan is applied when NOWPayments reports the payment finished. Buying the same plan again extends the running period; a different plan, or a workspace paying by card, is refused.
Body
| Field | Type | Notes | |
|---|---|---|---|
plan | core | core_ai | team | required | |
months | one of several shapes | required | |
pay_currency | string | required | pattern ^[a-z0-9]{2,20}$ |
return_path | string | optional | length 0–200; default "/settings/billing" |
Request
curl -X POST https://www.chatrail.dev/v1/billing/crypto/checkout \
-H "Content-Type: application/json" \
-d '{
"plan": "core_ai",
"months": 12,
"pay_currency": "usdttrc20",
"return_path": "/dashboard/billing"
}'Responses
| Status | Meaning |
|---|---|
201 | The order id and the invoice URL to redirect the browser to. |
400 | The plan, period or currency is not one that can be bought. |
404 | Crypto payments are not configured. |
409 | The workspace pays by card, or has a different plan prepaid. |
/v1/billing/plan-change-check
Check whether a plan change is possible
Dashboard session
Whether moving to the given plan is allowed, and if not, how many connected numbers would have to be disconnected first. A downgrade is refused rather than applied when it would strand a number over the target plan's limit, because the alternatives are a silently suspended number — whose linked device stops working and needs a QR re-scan — and a customer permanently exceeding the plan they pay for. Changes nothing and spends nothing, so it needs only billing:read.
Body
| Field | Type | Notes | |
|---|---|---|---|
plan | core | core_ai | team | required |
Request
curl -X POST https://www.chatrail.dev/v1/billing/plan-change-check \
-H "Content-Type: application/json" \
-d '{
"plan": "core"
}'Responses
| Status | Meaning |
|---|---|
200 | allowed: true, or allowed: false with the limit, the current count and mustDisconnect. |
400 | The plan is not one that can be bought. |
/v1/billing/plans
List paid plans
Dashboard session
The plans a checkout can be started for, with the price. purchasable is false when the plan has no Creem product configured yet. regional gives the plan's monthly price for workspaces that paired a WhatsApp number from a country with regional pricing. Before any number is paired it can be shown from the visitor's location with requires_number: true, meaning it applies only once a number from that country is paired. Null otherwise.
Request
curl -X GET https://www.chatrail.dev/v1/billing/plansResponses
| Status | Meaning |
|---|---|
200 | The paid plans, in the order shown on the pricing page. |
/v1/billing/portal
Open the customer portal
Dashboard session
A Creem-hosted page to update payment details or cancel — self-serve.
Request
curl -X POST https://www.chatrail.dev/v1/billing/portalResponses
| Status | Meaning |
|---|---|
200 | The portal URL to redirect the browser to. |
404 | This workspace has no billing account yet. |
/v1/billing/regional-price
The visitor's regional prices
No credential
For the public pricing pages: the regional monthly price of each plan in a country (?country=NG, else the visitor's location), or regional: null when there is none. It only shows the price; a workspace pays it once it pairs a WhatsApp number from that country.
Request
curl -X GET https://www.chatrail.dev/v1/billing/regional-priceResponses
| Status | Meaning |
|---|---|
200 | The prices for the visitor's country, or regional: null. |
Auth
Dashboard sign-in. Not used by API clients.
/v1/auth/logout
End the session
Dashboard session
Request
curl -X POST https://www.chatrail.dev/v1/auth/logoutResponses
| Status | Meaning |
|---|---|
204 | Signed out. |
/v1/auth/oauth/{provider}/callback
Finish a Google or GitHub sign-in
No credential
Where the provider sends the person back. Only an address the provider has verified is accepted; it lands in the same account an emailed code for that address would. Always redirects to the sign-in page — with a session cookie on success, or auth_error on failure.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
provider | path | string | required | `google` or `github`. |
Request
curl -X GET https://www.chatrail.dev/v1/auth/oauth/:provider/callbackResponses
| Status | Meaning |
|---|---|
302 | Redirect to the sign-in page. |
/v1/auth/oauth/{provider}/start
Start a Google or GitHub sign-in
No credential
A browser navigation, not an API call: redirects to the provider with a state value and a PKCE challenge held in a short-lived HttpOnly cookie. An optional same-origin next path is where the person lands afterwards.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
provider | path | string | required | `google` or `github`. |
Request
curl -X GET https://www.chatrail.dev/v1/auth/oauth/:provider/startResponses
| Status | Meaning |
|---|---|
302 | Redirect to the provider. |
404 | That provider is not configured. |
/v1/auth/providers
List sign-in providers
No credential
Which of Google and GitHub are switched on for this deployment, so a sign-in page only offers buttons that will work.
Request
curl -X GET https://www.chatrail.dev/v1/auth/providersResponses
| Status | Meaning |
|---|---|
200 | An object of provider name to boolean. |
/v1/auth/request-code
Request a sign-in code
No credential
Always answers 202, whether or not the address has an account. It is not an existence check and must not be presented as one.
Body
| Field | Type | Notes | |
|---|---|---|---|
email | string (email) | required | length 0–320; email format |
Request
curl -X POST https://www.chatrail.dev/v1/auth/request-code \
-H "Content-Type: application/json" \
-d '{
"email": "you@company.com"
}'Responses
| Status | Meaning |
|---|---|
202 | A code has been sent if that address has an account. |
/v1/auth/session
Read the current session
Dashboard session
Request
curl -X GET https://www.chatrail.dev/v1/auth/sessionResponses
| Status | Meaning |
|---|---|
200 | The signed-in user and workspace. |
401 | Not signed in. |
/v1/auth/verify
Exchange a code for a session
No credential
Every failure returns the same error — no code, wrong code, expired, too many attempts. Distinguishing them would reintroduce an oracle.
Body
| Field | Type | Notes | |
|---|---|---|---|
email | string (email) | required | length 0–320; email format |
code | string | required | pattern ^\d{6}$ |
Request
curl -X POST https://www.chatrail.dev/v1/auth/verify \
-H "Content-Type: application/json" \
-d '{
"email": "you@company.com",
"code": "123456"
}'Responses
| Status | Meaning |
|---|---|
200 | A session cookie is set. |
401 | That code is not valid. |
/v1/profile
Read the signed-in profile
Dashboard session
Request
curl -X GET https://www.chatrail.dev/v1/profileResponses
| Status | Meaning |
|---|---|
200 | Personal account details. |
401 | Not signed in. |
/v1/profile
Update the display name
Dashboard session
Body
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | length 1–120 |
Request
curl -X PATCH https://www.chatrail.dev/v1/profile \
-H "Content-Type: application/json" \
-d '{
"name": "Ada Lovelace"
}'Responses
| Status | Meaning |
|---|---|
200 | The updated profile. |
400 | The name is empty or too long. |
/v1/profile/heard-from
Record how you heard about ChatRail
Dashboard session
Asked once on the getting-started page. skipped records a dismissal so the question is not shown again. Answering again replaces the answer.
Body
| Field | Type | Notes | |
|---|---|---|---|
source | google | ai_assistant | x | linkedin | youtube | reddit | github | n8n | friend | other | skipped | required | |
detail | string | optional | length 0–200 |
Request
curl -X PUT https://www.chatrail.dev/v1/profile/heard-from \
-H "Content-Type: application/json" \
-d '{
"source": "linkedin",
"detail": "A post about WhatsApp bots"
}'Responses
| Status | Meaning |
|---|---|
200 | The updated profile. |
400 | The source was not one of the listed values. |
/v1/profile/notification-preferences
Read notification preferences
Dashboard session
Request
curl -X GET https://www.chatrail.dev/v1/profile/notification-preferencesResponses
| Status | Meaning |
|---|---|
200 | The signed-in user’s email notification preferences. |
/v1/profile/notification-preferences
Update notification preferences
Dashboard session
Body
| Field | Type | Notes | |
|---|---|---|---|
security | boolean | optional | |
connectionFailures | boolean | optional | |
deliveryProblems | boolean | optional | |
aiLimits | boolean | optional | |
billingUsage | boolean | optional | |
onboarding | boolean | optional |
Request
curl -X PATCH https://www.chatrail.dev/v1/profile/notification-preferences \
-H "Content-Type: application/json" \
-d '{
"deliveryProblems": false,
"aiLimits": true
}'Responses
| Status | Meaning |
|---|---|
200 | The updated preferences. |
400 | The update was empty or unknown. |
/v1/profile/sessions
List active account sessions
Dashboard session
Lists only the signed-in user’s live sessions and never exposes session tokens.
Request
curl -X GET https://www.chatrail.dev/v1/profile/sessionsResponses
| Status | Meaning |
|---|---|
200 | Active device sessions, with the current session marked. |
/v1/profile/sessions/revoke-others
Sign out other devices
Dashboard session
Request
curl -X POST https://www.chatrail.dev/v1/profile/sessions/revoke-othersResponses
| Status | Meaning |
|---|---|
200 | The number of other sessions revoked. |
/v1/profile/sessions/{sessionId}
Sign out a device
Dashboard session
A session belonging to another user is reported as not found.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
sessionId | path | string | required | Session id. |
Request
curl -X DELETE https://www.chatrail.dev/v1/profile/sessions/:sessionIdResponses
| Status | Meaning |
|---|---|
204 | Revoked. |
404 | No such active session. |
MCP
Workspace tools for a coding assistant.
/v1/mcp
MCP endpoint
API key · mcp:read
Model Context Protocol over a stateless streamable HTTP transport. OAuth 2.1 access tokens and workspace API keys are both accepted. Read-only by default; mutating tools preview and apply only when called again with confirm: true. No tool returns message content.
Request
curl -X POST https://www.chatrail.dev/v1/mcp \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | An MCP response. |
401 | Authentication is required. |
402 | MCP is not on this plan. |
/v1/mcp/tools
List MCP tools
API key
Available to dashboard sessions for setup and to API keys with mcp:read.
Request
curl -X GET https://www.chatrail.dev/v1/mcp/tools \
-H "Authorization: Bearer $CHATRAIL_API_KEY"Responses
| Status | Meaning |
|---|---|
200 | The tools this credential may call. |
Operational
Health and metrics.
/healthz
Liveness
No credential
Never checks dependencies. Point a restart policy here, not a traffic probe.
Request
curl -X GET https://www.chatrail.dev/healthzResponses
| Status | Meaning |
|---|---|
200 | The process is alive. |
/healthz/worker
Worker liveness
No credential
Whether the background worker has recorded a heartbeat in the last five minutes. For an external uptime monitor: the worker runs the other alerts, so it cannot report its own death.
Request
curl -X GET https://www.chatrail.dev/healthz/workerResponses
| Status | Meaning |
|---|---|
200 | The worker is alive. |
503 | No heartbeat in five minutes, or none ever recorded. |
/metrics
Prometheus metrics
No credential
Not registered unless METRICS_TOKEN is configured. No label carries a workspace, connection, number or session name.
Request
curl -X GET https://www.chatrail.dev/metricsResponses
| Status | Meaning |
|---|---|
200 | Exposition format. |
401 | Missing or wrong token. |
/readyz
Readiness
No credential
Checks dependencies. Point the load balancer here.
Request
curl -X GET https://www.chatrail.dev/readyzResponses
| Status | Meaning |
|---|---|
200 | Ready. |
503 | A dependency is unavailable. |
/v1/openapi.json
This document
No credential
Request
curl -X GET https://www.chatrail.dev/v1/openapi.jsonResponses
| Status | Meaning |
|---|---|
200 | The OpenAPI document. |
Internal
Called by the transport, not by you.
/v1/billing/creem/webhook
Creem webhook
No credential
Called by Creem, not by you. Verified against creem-signature, a plain HMAC-SHA256 of the exact request body — Creem retries a non-2xx for up to 24 hours, so redeliveries are expected and deduplicated by event id.
Request
curl -X POST https://www.chatrail.dev/v1/billing/creem/webhookResponses
| Status | Meaning |
|---|---|
200 | Accepted — duplicate: true if this event id was already processed. |
/v1/billing/nowpayments/webhook
NOWPayments payment notification
No credential
Called by NOWPayments, not by you. Verified against x-nowpayments-sig, an HMAC-SHA512 of the key-sorted JSON body. Repeated notifications are deduplicated by payment id and status.
Request
curl -X POST https://www.chatrail.dev/v1/billing/nowpayments/webhookResponses
| Status | Meaning |
|---|---|
200 | Accepted — duplicate: true if this notification was already processed. |
/v1/provider/{providerName}/sessions/{sessionId}
Signed provider callback
No credential
Called by the WhatsApp transport, not by you. The URL names the session; the body is signed with the connection’s own key (X-Webhook-Hmac, HMAC-SHA512 over the exact bytes). Replaces the token URL, which put a credential in every proxy log.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
providerName | path | string | required | Transport name, e.g. `waha`. |
sessionId | path | string | required | The transport’s session name. |
Request
curl -X POST https://www.chatrail.dev/v1/provider/:providerName/sessions/:sessionIdResponses
| Status | Meaning |
|---|---|
202 | Handled or ignored. |
404 | Unknown session or bad signature. |
/v1/provider/{providerName}/{token}
Provider callback
No credential
Called by the WhatsApp transport, not by you. Authenticated by the per-connection token returned once when the connection was created. Redeliveries are deduplicated.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
providerName | path | string | required | Transport name, e.g. `waha`. |
token | path | string | required | The connection callback token. |
Request
curl -X POST https://www.chatrail.dev/v1/provider/:providerName/:tokenResponses
| Status | Meaning |
|---|---|
202 | Handled or ignored. |
404 | Unknown callback. |