API REFERENCE

Every endpoint

Send, schedule and receive WhatsApp messages through a linked-device session.

Operations
104
Base URL
https://www.chatrail.dev
Spec
OpenAPI 3.1.0
BEFORE YOU START

Two things worth knowing

Accepted is not delivered.

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.

A retry must not send twice.

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

Messages

Sending, delivery state and export.

POST /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Body

FieldTypeNotes
toone of several shapesrequired
statetyping | recording | pausedrequired

Request

Terminal
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

StatusMeaning
204Shown.
404No such connection.
409The connection is not ready.
422The connection's engine cannot show typing.
GET /v1/messages

List messages

API key · messages:read

Newest first, cursor paginated. Offsets are not offered: they skip and duplicate.

Parameters

NameInTypeNotes
limitqueryintegeroptional1 to 100; default 25
cursorquerystringoptionallength 0–200
connectionquerystringoptionallength 1–40
statusqueryscheduled | accepted | queued | submitting | submitted | sent | delivered | read | failed | canceledoptional
directionqueryinbound | outboundoptional
qquerystringoptionallength 1–120

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/messages \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200A page of messages and a next_cursor.
GET /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

NameInTypeNotes
connectionquerystringoptionallength 1–40
statusquerystringoptionallength 1–20
directionqueryinbound | outboundoptional
daysqueryintegeroptional1 to 365; default 30

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/messages/export \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200A CSV stream.
POST /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

FieldTypeNotes
connectionstringrequiredlength 1–40
toone of several shapesrequired
bodystringoptionallength 0–4096
mediaobjectoptional
locationobjectoptional
contactobjectoptional
pollobjectoptional
reply_tostring (uuid)optionaluuid format
mentionsarray of stringoptional
link_previewbooleanoptional
contextobjectoptional
metadataobjectoptional
pause_aibooleanoptional
context_sensitivitynormal | sensitive | restrictedoptional
send_atstring (date-time)optionaldate-time format
timezonestringoptionallength 1–64

Request

Terminal
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

StatusMeaning
202Accepted and queued.
400The body, recipient or attachment was rejected.
402The plan does not include this. The message names what would allow it.
409The connection is not ready, or an identical Idempotency-Key is in flight.
422The Idempotency-Key was reused with a different body.
429Quota or rate limit.
GET /v1/messages/{id}

Get a message

API key · messages:read

Parameters

NameInTypeNotes
idpathstringrequiredMessage id.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/messages/:id \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The message and its delivery timeline.
404No such message.
PATCH /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

NameInTypeNotes
idpathstringrequiredMessage id.

Body

FieldTypeNotes
bodystringoptionallength 0–4096
contextobjectoptional
context_sensitivitynormal | sensitive | restrictedoptional

Request

Terminal
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

StatusMeaning
200The message, with edited_at set.
404No such message.
409Not editable: received, not a text message, deleted, or past the window.
POST /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

NameInTypeNotes
idpathstringrequiredMessage id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/messages/:id/cancel \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Cancelled.
404No such message.
409Already handed to the provider.
POST /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

NameInTypeNotes
idpathstringrequiredInbound message id.

Body

FieldTypeNotes
reply_to_message_idone of several shapesrequired

Request

Terminal
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

StatusMeaning
200The corrected link, and what it overruled.
400The target is not an outbound message in the same conversation.
404No such message.
409Only an inbound message has a correlation to correct.
GET /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

NameInTypeNotes
idpathstringrequiredInbound message id.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/messages/:id/media \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The file.
404No downloadable media on this message.
503This deployment cannot retrieve media from the transport.
POST /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

NameInTypeNotes
idpathstringrequiredMessage id.

Body

FieldTypeNotes
emojistringrequiredlength 0–16

Request

Terminal
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

StatusMeaning
200The message, with reaction set.
404No such message.
409Not possible for this message, or WhatsApp refused.
422The connection's engine does not support reactions.
POST /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

NameInTypeNotes
idpathstringrequiredMessage id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/messages/:id/read \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The message, with seen_at set.
404No such message.
409Only a received message can be marked read.
POST /v1/messages/{id}/reschedule

Move a scheduled message

API key · messages:write

Parameters

NameInTypeNotes
idpathstringrequiredMessage id.

Body

FieldTypeNotes
send_atstring (date-time)requireddate-time format
timezonestringoptionallength 1–64

Request

Terminal
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

StatusMeaning
200Rescheduled.
409No longer scheduled.
POST /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

NameInTypeNotes
idpathstringrequiredMessage id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/messages/:id/revoke \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The message, with deleted_at set.
404No such message.
409Not deletable: received, already deleted, or past the window.
GET /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

NameInTypeNotes
phonequerystringrequiredlength 5–40
connectionquerystringrequiredlength 1–40

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/numbers/check \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The answer, or checked: false.
404No such connection.
429No lookup budget left; Retry-After says how long to wait.
POST /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

FieldTypeNotes
connectionstringrequiredlength 1–40
phonesarray of stringrequired

Request

Terminal
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

StatusMeaning
200One result per number.
400No numbers, or more than 10.
404No such connection.
CONNECTIONS

Connections

WhatsApp numbers and their pairing lifecycle.

GET /v1/connections

List connections

API key · connections:read

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/connections \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Every live connection in the workspace.
POST /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

FieldTypeNotes
slugstringrequiredpattern ^[a-z0-9][a-z0-9-]{0,38}[a-z0-9]$
namestringoptionallength 1–80
proxyobjectoptional

Request

Terminal
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

StatusMeaning
201Created, with the one-time callback token.
400The request is not valid, such as a proxy that is a hostname or a private address.
402The plan has no room for another number, or does not include proxies.
409That slug is taken.
422The WhatsApp engine cannot use a proxy.
GET /v1/connections/{connection}

Get a connection

API key · connections:read

Parameters

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/connections/:connection \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The connection and its state.
404No such connection.
PATCH /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Body

FieldTypeNotes
reject_callsbooleanoptional
proxyone of several shapesoptional

Request

Terminal
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

StatusMeaning
200The updated connection.
400A setting is not valid, such as a proxy that is a hostname or a private address.
402The plan does not include proxies.
404No such connection.
409The proxy cannot be changed: the number is linked.
422The number's WhatsApp engine cannot use a proxy.
DELETE /v1/connections/{connection}

Delete a connection

API key · connections:write

Soft deletes it and queues removal of its data.

Parameters

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Request

Terminal
curl -X DELETE https://www.chatrail.dev/v1/connections/:connection \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
204Deleted.
404No such connection.
GET /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.
limitqueryintegeroptional1 to 200; default 100
offsetqueryintegeroptional0 to 100000; default 0

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/connections/:connection/groups \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200A page of groups.
404No such connection.
502The transport returned an unreadable response.
503The transport is unavailable.
POST /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Body

FieldTypeNotes
methodqr | codeoptionaldefault "qr"
phonestringoptionallength 5–40

Request

Terminal
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

StatusMeaning
200A grant id and its expiry.
409The connection cannot be paired from its current state.
503No transport is configured on this deployment.
GET /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.
grantIdpathstringrequiredGrant id.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/connections/:connection/qr/:grantId \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The pairing payload.
404Unknown, used or expired.
POST /v1/connections/{connection}/resume

Resume a suspended connection

API key · connections:write

Parameters

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/connections/:connection/resume \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Resumed.
409Not suspended.
POST /v1/connections/{connection}/suspend

Suspend a connection

API key · connections:write

Parameters

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/connections/:connection/suspend \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Suspended.
409Not in a suspendable state.
GROUPS

Groups

Creating and running WhatsApp groups from a connected number.

POST /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Body

FieldTypeNotes
namestringrequiredlength 1–100
participantsarray of stringrequired

Request

Terminal
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

StatusMeaning
201The new group, with its members.
402Not on this plan.
429Over the member-add guardrail for this hour.
GET /v1/connections/{connection}/groups/{group}

Get a group

API key · connections:read

Name, description, settings and members with their roles.

Parameters

NameInTypeNotes
connectionpathstringrequiredConnection slug.
grouppathstringrequiredGroup id.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/connections/:connection/groups/:group \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The group.
404No such group for this number.
PATCH /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.
grouppathstringrequiredGroup id.

Body

FieldTypeNotes
namestringoptionallength 1–100
descriptionstringoptionallength 0–2048
messages_admins_onlybooleanoptional
info_admins_onlybooleanoptional

Request

Terminal
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

StatusMeaning
200The updated group.
409WhatsApp refused; not an admin?
GET /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.
grouppathstringrequiredGroup id.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/connections/:connection/groups/:group/invite \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The link.
POST /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.
grouppathstringrequiredGroup id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/connections/:connection/groups/:group/invite/revoke \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The new link.
POST /v1/connections/{connection}/groups/{group}/leave

Leave a group

API key · connections:write

This number leaves the group.

Parameters

NameInTypeNotes
connectionpathstringrequiredConnection slug.
grouppathstringrequiredGroup id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/connections/:connection/groups/:group/leave \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
204Left.
POST /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.
grouppathstringrequiredGroup id.

Body

FieldTypeNotes
actionadd | remove | promote | demoterequired
participantsarray of stringrequired

Request

Terminal
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

StatusMeaning
200The group after the change.
429Over the member-add guardrail for this hour.
SCHEDULES

Schedules

Recurring sends.

GET /v1/schedules

List schedules

API key · schedules:read

Parameters

NameInTypeNotes
statusqueryactive | paused | completed | canceledoptional

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/schedules \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Every schedule in the workspace.
POST /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

FieldTypeNotes
namestringrequiredlength 1–80
timezonestringrequiredlength 1–64
recurrenceone of several shapesrequired
messageobjectrequired
starts_atstring (date-time)optionaldate-time format
ends_atstring (date-time)optionaldate-time format
max_occurrencesintegeroptional1 to 10000

Request

Terminal
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

StatusMeaning
201Created, with the resolved next_run_at.
400Unknown timezone, or a recurrence that would never fire.
402The plan does not include scheduling.
GET /v1/schedules/{id}

Get a schedule

API key · schedules:read

Parameters

NameInTypeNotes
idpathstringrequiredSchedule id.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/schedules/:id \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The schedule.
404No such schedule.
POST /v1/schedules/{id}/cancel

Cancel a schedule

API key · schedules:write

Permanent. A cancelled schedule cannot be restarted.

Parameters

NameInTypeNotes
idpathstringrequiredSchedule id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/schedules/:id/cancel \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The updated schedule.
409Not in a state that allows it.
POST /v1/schedules/{id}/pause

Pause a schedule

API key · schedules:write

Keeps the schedule and stops it firing.

Parameters

NameInTypeNotes
idpathstringrequiredSchedule id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/schedules/:id/pause \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The updated schedule.
409Not in a state that allows it.
POST /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

NameInTypeNotes
idpathstringrequiredSchedule id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/schedules/:id/resume \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The updated schedule.
409Not in a state that allows it.
WEBHOOKS

Webhooks

Where inbound events and delivery updates are sent.

GET /v1/webhook-endpoints

List webhook endpoints

API key · webhooks:read

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/webhook-endpoints \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Endpoints, without their signing secrets.
POST /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

FieldTypeNotes
urlstring (uri)requiredlength 0–2048
descriptionstringoptionallength 0–200
eventsarray of stringrequired
connectionstringoptionallength 1–40

Request

Terminal
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

StatusMeaning
201Created, with the one-time signing secret.
400The URL is not publicly reachable over https.
402Scoping to one connection is not on this plan.
DELETE /v1/webhook-endpoints/{id}

Delete a webhook endpoint

API key · webhooks:write

Parameters

NameInTypeNotes
idpathstringrequiredEndpoint id.

Request

Terminal
curl -X DELETE https://www.chatrail.dev/v1/webhook-endpoints/:id \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
204Deleted.
404No such endpoint.
POST /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

NameInTypeNotes
idpathstringrequiredEndpoint id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/webhook-endpoints/:id/enable \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The endpoint, now active.
404No such endpoint.
OPT-OUTS

Opt-outs

People who asked not to be messaged. Sends to them are refused.

GET /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

NameInTypeNotes
limitqueryintegeroptional1 to 500; default 100
cursorquerystring (uuid)optionaluuid format

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/opt-outs \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200A page of opt-outs, with next_cursor when there is more.
POST /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

FieldTypeNotes
phonestringrequiredlength 1–40

Request

Terminal
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

StatusMeaning
200Already on the list.
201Added.
400Not a valid phone number.
DELETE /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

NameInTypeNotes
phonepathstringrequiredThe number in international form, with or without the leading +.

Request

Terminal
curl -X DELETE https://www.chatrail.dev/v1/opt-outs/:phone \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
204Removed.
400Not a valid phone number.
404That number is not on the list.
AI

AI

Optional, per connection, off by default.

GET /v1/ai/credentials

List credentials

API key · ai:read

Fingerprints and metadata only; the key itself is never returned.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/ai/credentials \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Credential metadata.
POST /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

FieldTypeNotes
provideropenai_compatible | anthropicrequired
labelstringrequiredlength 1–80
api_keystringrequiredlength 8–8192
base_urlstring (uri)optional

Request

Terminal
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

StatusMeaning
201Stored.
402AI is not on this plan.
DELETE /v1/ai/credentials/{id}

Delete a credential

API key · ai:write

Parameters

NameInTypeNotes
idpathstringrequiredCredential id.

Request

Terminal
curl -X DELETE https://www.chatrail.dev/v1/ai/credentials/:id \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
204Deleted.
404No such credential.
POST /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

NameInTypeNotes
idpathstringrequiredCredential id.

Body

FieldTypeNotes
modelstringrequiredlength 1–120

Request

Terminal
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

StatusMeaning
200The provider answered.
404No such credential.
502The provider refused or failed; the body says why.
GET /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/ai/policies/:connection \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The policy.
404No policy is set.
PUT /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

NameInTypeNotes
connectionpathstringrequiredConnection slug.

Body

FieldTypeNotes
credential_sourcebyok | managedoptionaldefault "byok"
credential_idone of several shapesoptional
enabledbooleanrequired
modedraft | autorequired
modelstringoptionallength 1–120
system_instructionstringrequiredlength 1–4000
max_context_bytesintegeroptional256 to 32768; default 8192
max_output_tokensintegeroptional32 to 2000; default 300
include_historybooleanoptionaldefault false
humanizebooleanoptionaldefault true
human_takeover_minutesintegeroptional0 to 43200; default 1440
reply_delay_secondsintegeroptional0 to 120; default 5
group_repliesoff | addressed | alloptionaldefault "addressed"
monthly_cost_limit_microsone of several shapesoptionaldefault null
input_cost_micros_per_millionintegeroptional0 to 9007199254740991; default 0
output_cost_micros_per_millionintegeroptional0 to 9007199254740991; default 0

Request

Terminal
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

StatusMeaning
200The policy.
402AI is not on this plan.
GET /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

Terminal
curl -X GET https://www.chatrail.dev/v1/ai/quality \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The summary.
GET /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

Terminal
curl -X GET https://www.chatrail.dev/v1/ai/runs \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Recent runs.
POST /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

NameInTypeNotes
idpathstringrequiredAI run id.

Body

FieldTypeNotes
reasonwrong_context | wrong_correlation | inaccurate | tone | not_needed | needs_human | otheroptional

Request

Terminal
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

StatusMeaning
204Dismissed.
409The draft cannot be dismissed.
POST /v1/ai/runs/{id}/send

Send an AI draft

API key · ai:write

Accepts the pending draft through the normal durable message outbox.

Parameters

NameInTypeNotes
idpathstringrequiredAI run id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/ai/runs/:id/send \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The accepted message id.
409The draft cannot be sent.
TEAM

Team

Membership and invitations. Dashboard sessions only.

GET /v1/members

List members and pending invitations

Dashboard session

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/members

Responses

StatusMeaning
200Members, invitations, and the roles this caller may assign.
POST /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

FieldTypeNotes
tokenstringrequiredlength 20–200

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/members/accept \
  -H "Content-Type: application/json" \
  -d '{
    "token": "the token from the invitation link"
  }'

Responses

StatusMeaning
200Joined.
401Sign in as the invited address first.
403The invitation was sent to a different address.
404The invitation is not valid.
POST /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

FieldTypeNotes
emailstring (email)requiredlength 0–254; email format
roleowner | admin | developer | viewer | billingrequired

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/members/invitations \
  -H "Content-Type: application/json" \
  -d '{
    "email": "colleague@company.com",
    "role": "viewer"
  }'

Responses

StatusMeaning
201Invited.
402Member management is not on this plan.
403You cannot assign that role.
409Already a member.
DELETE /v1/members/invitations/{id}

Revoke an invitation

Dashboard session

Parameters

NameInTypeNotes
idpathstringrequiredInvitation id.

Request

Terminal
curl -X DELETE https://www.chatrail.dev/v1/members/invitations/:id

Responses

StatusMeaning
204Revoked.
404No such invitation.
POST /v1/members/invitations/{id}/resend

Resend an invitation

Dashboard session

Emails the invitation again with a fresh link. Dashboard only.

Parameters

NameInTypeNotes
idpathstringrequiredInvitation id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/members/invitations/:id/resend

Responses

StatusMeaning
200Resent.
404No such invitation.
PATCH /v1/members/{id}

Change a member's role

Dashboard session

Parameters

NameInTypeNotes
idpathstringrequiredUser id.

Body

FieldTypeNotes
roleowner | admin | developer | viewer | billingrequired

Request

Terminal
curl -X PATCH https://www.chatrail.dev/v1/members/:id \
  -H "Content-Type: application/json" \
  -d '{
    "role": "developer"
  }'

Responses

StatusMeaning
200The updated member.
403You cannot assign or act on that role.
409A workspace must keep an owner.
DELETE /v1/members/{id}

Remove a member

Dashboard session

Parameters

NameInTypeNotes
idpathstringrequiredUser id.

Request

Terminal
curl -X DELETE https://www.chatrail.dev/v1/members/:id

Responses

StatusMeaning
204Removed.
409A workspace must keep an owner.
WORKSPACE

Workspace

Identity, credentials, plan and usage.

GET /v1/api-keys

List API keys

Dashboard session

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/api-keys

Responses

StatusMeaning
200Key metadata and prefixes, never the keys.
POST /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

FieldTypeNotes
namestringrequiredlength 1–80
scopesarray 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:readrequired
environmentlive | testoptionaldefault "live"

Request

Terminal
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

StatusMeaning
201The key, once.
403Your role cannot grant those scopes.
POST /v1/api-keys/{id}/revoke

Revoke an API key

Dashboard session

Parameters

NameInTypeNotes
idpathstringrequiredKey id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/api-keys/:id/revoke

Responses

StatusMeaning
200Revoked, immediately.
404No such key.
POST /v1/api-keys/{id}/rotate

Rotate an API key

Dashboard session

Issues a replacement and revokes the old one.

Parameters

NameInTypeNotes
idpathstringrequiredKey id.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/api-keys/:id/rotate

Responses

StatusMeaning
200The new key, once.
404No such key.
GET /v1/usage

Usage counts

API key · usage:read

Counts only. A count of messages is not a message; no content is reachable here.

Parameters

NameInTypeNotes
daysqueryintegeroptional1 to 365

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/usage \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200Aggregated counts for the workspace.
GET /v1/whoami

Confirm a credential

API key

Shows exactly what the presented credential can do. The first call to make.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/whoami \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The principal, its scopes and its workspace.
GET /v1/workspace

Get the current workspace

Dashboard session

Includes the plan and what has been used against it.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/workspace

Responses

StatusMeaning
200The workspace, the caller role and the plan.
PATCH /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

FieldTypeNotes
namestringrequired

Request

Terminal
curl -X PATCH https://www.chatrail.dev/v1/workspace \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Kettle & Co"
  }'

Responses

StatusMeaning
200The workspace id, slug and new name.
400The name is empty or longer than 80 characters.
BILLING

Billing

Checkout, the customer portal and Creem webhooks.

GET /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

Terminal
curl -X GET https://www.chatrail.dev/v1/billing

Responses

StatusMeaning
200The billing account, or the free-tier default if there is none yet.
POST /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

FieldTypeNotes
plancore | core_ai | teamrequired
return_pathstringoptionallength 0–200; default "/settings/billing"

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/billing/checkout \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "core_ai",
    "return_path": "/settings/billing"
  }'

Responses

StatusMeaning
201The checkout id and URL to redirect the browser to.
404Billing is not configured for this workspace.
GET /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

Terminal
curl -X GET https://www.chatrail.dev/v1/billing/crypto

Responses

StatusMeaning
200Options and status. available: false when crypto is not configured.
POST /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

FieldTypeNotes
plancore | core_ai | teamrequired
monthsone of several shapesrequired
pay_currencystringrequiredpattern ^[a-z0-9]{2,20}$
return_pathstringoptionallength 0–200; default "/settings/billing"

Request

Terminal
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

StatusMeaning
201The order id and the invoice URL to redirect the browser to.
400The plan, period or currency is not one that can be bought.
404Crypto payments are not configured.
409The workspace pays by card, or has a different plan prepaid.
POST /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

FieldTypeNotes
plancore | core_ai | teamrequired

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/billing/plan-change-check \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "core"
  }'

Responses

StatusMeaning
200allowed: true, or allowed: false with the limit, the current count and mustDisconnect.
400The plan is not one that can be bought.
GET /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

Terminal
curl -X GET https://www.chatrail.dev/v1/billing/plans

Responses

StatusMeaning
200The paid plans, in the order shown on the pricing page.
POST /v1/billing/portal

Open the customer portal

Dashboard session

A Creem-hosted page to update payment details or cancel — self-serve.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/billing/portal

Responses

StatusMeaning
200The portal URL to redirect the browser to.
404This workspace has no billing account yet.
GET /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

Terminal
curl -X GET https://www.chatrail.dev/v1/billing/regional-price

Responses

StatusMeaning
200The prices for the visitor's country, or regional: null.
AUTH

Auth

Dashboard sign-in. Not used by API clients.

POST /v1/auth/logout

End the session

Dashboard session

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/auth/logout

Responses

StatusMeaning
204Signed out.
GET /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

NameInTypeNotes
providerpathstringrequired`google` or `github`.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/auth/oauth/:provider/callback

Responses

StatusMeaning
302Redirect to the sign-in page.
GET /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

NameInTypeNotes
providerpathstringrequired`google` or `github`.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/auth/oauth/:provider/start

Responses

StatusMeaning
302Redirect to the provider.
404That provider is not configured.
GET /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

Terminal
curl -X GET https://www.chatrail.dev/v1/auth/providers

Responses

StatusMeaning
200An object of provider name to boolean.
POST /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

FieldTypeNotes
emailstring (email)requiredlength 0–320; email format

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/auth/request-code \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@company.com"
  }'

Responses

StatusMeaning
202A code has been sent if that address has an account.
GET /v1/auth/session

Read the current session

Dashboard session

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/auth/session

Responses

StatusMeaning
200The signed-in user and workspace.
401Not signed in.
POST /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

FieldTypeNotes
emailstring (email)requiredlength 0–320; email format
codestringrequiredpattern ^\d{6}$

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/auth/verify \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@company.com",
    "code": "123456"
  }'

Responses

StatusMeaning
200A session cookie is set.
401That code is not valid.
GET /v1/profile

Read the signed-in profile

Dashboard session

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/profile

Responses

StatusMeaning
200Personal account details.
401Not signed in.
PATCH /v1/profile

Update the display name

Dashboard session

Body

FieldTypeNotes
namestringrequiredlength 1–120

Request

Terminal
curl -X PATCH https://www.chatrail.dev/v1/profile \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Lovelace"
  }'

Responses

StatusMeaning
200The updated profile.
400The name is empty or too long.
PUT /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

FieldTypeNotes
sourcegoogle | ai_assistant | x | linkedin | youtube | reddit | github | n8n | friend | other | skippedrequired
detailstringoptionallength 0–200

Request

Terminal
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

StatusMeaning
200The updated profile.
400The source was not one of the listed values.
GET /v1/profile/notification-preferences

Read notification preferences

Dashboard session

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/profile/notification-preferences

Responses

StatusMeaning
200The signed-in user’s email notification preferences.
PATCH /v1/profile/notification-preferences

Update notification preferences

Dashboard session

Body

FieldTypeNotes
securitybooleanoptional
connectionFailuresbooleanoptional
deliveryProblemsbooleanoptional
aiLimitsbooleanoptional
billingUsagebooleanoptional
onboardingbooleanoptional

Request

Terminal
curl -X PATCH https://www.chatrail.dev/v1/profile/notification-preferences \
  -H "Content-Type: application/json" \
  -d '{
    "deliveryProblems": false,
    "aiLimits": true
  }'

Responses

StatusMeaning
200The updated preferences.
400The update was empty or unknown.
GET /v1/profile/sessions

List active account sessions

Dashboard session

Lists only the signed-in user’s live sessions and never exposes session tokens.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/profile/sessions

Responses

StatusMeaning
200Active device sessions, with the current session marked.
POST /v1/profile/sessions/revoke-others

Sign out other devices

Dashboard session

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/profile/sessions/revoke-others

Responses

StatusMeaning
200The number of other sessions revoked.
DELETE /v1/profile/sessions/{sessionId}

Sign out a device

Dashboard session

A session belonging to another user is reported as not found.

Parameters

NameInTypeNotes
sessionIdpathstringrequiredSession id.

Request

Terminal
curl -X DELETE https://www.chatrail.dev/v1/profile/sessions/:sessionId

Responses

StatusMeaning
204Revoked.
404No such active session.
MCP

MCP

Workspace tools for a coding assistant.

POST /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

Terminal
curl -X POST https://www.chatrail.dev/v1/mcp \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200An MCP response.
401Authentication is required.
402MCP is not on this plan.
GET /v1/mcp/tools

List MCP tools

API key

Available to dashboard sessions for setup and to API keys with mcp:read.

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/mcp/tools \
  -H "Authorization: Bearer $CHATRAIL_API_KEY"

Responses

StatusMeaning
200The tools this credential may call.
OPERATIONAL

Operational

Health and metrics.

GET /healthz

Liveness

No credential

Never checks dependencies. Point a restart policy here, not a traffic probe.

Request

Terminal
curl -X GET https://www.chatrail.dev/healthz

Responses

StatusMeaning
200The process is alive.
GET /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

Terminal
curl -X GET https://www.chatrail.dev/healthz/worker

Responses

StatusMeaning
200The worker is alive.
503No heartbeat in five minutes, or none ever recorded.
GET /metrics

Prometheus metrics

No credential

Not registered unless METRICS_TOKEN is configured. No label carries a workspace, connection, number or session name.

Request

Terminal
curl -X GET https://www.chatrail.dev/metrics

Responses

StatusMeaning
200Exposition format.
401Missing or wrong token.
GET /readyz

Readiness

No credential

Checks dependencies. Point the load balancer here.

Request

Terminal
curl -X GET https://www.chatrail.dev/readyz

Responses

StatusMeaning
200Ready.
503A dependency is unavailable.
GET /v1/openapi.json

This document

No credential

Request

Terminal
curl -X GET https://www.chatrail.dev/v1/openapi.json

Responses

StatusMeaning
200The OpenAPI document.
INTERNAL

Internal

Called by the transport, not by you.

POST /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

Terminal
curl -X POST https://www.chatrail.dev/v1/billing/creem/webhook

Responses

StatusMeaning
200Accepted — duplicate: true if this event id was already processed.
POST /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

Terminal
curl -X POST https://www.chatrail.dev/v1/billing/nowpayments/webhook

Responses

StatusMeaning
200Accepted — duplicate: true if this notification was already processed.
POST /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

NameInTypeNotes
providerNamepathstringrequiredTransport name, e.g. `waha`.
sessionIdpathstringrequiredThe transport’s session name.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/provider/:providerName/sessions/:sessionId

Responses

StatusMeaning
202Handled or ignored.
404Unknown session or bad signature.
POST /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

NameInTypeNotes
providerNamepathstringrequiredTransport name, e.g. `waha`.
tokenpathstringrequiredThe connection callback token.

Request

Terminal
curl -X POST https://www.chatrail.dev/v1/provider/:providerName/:token

Responses

StatusMeaning
202Handled or ignored.
404Unknown callback.