chatrail-dev Messages API
Sending, delivery state and export.
Sending, delivery state and export.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/chatrail-dev:chatrail-dev-messages-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: ChatRail Messages API
version: 1.0.0
description: 'Send, schedule and receive WhatsApp messages through a linked-device session.
**Two things the API is careful about, which shape every endpoint below.**
*Accepted is not delivered.* A 202 means the message is durably stored and queued. Only the `status` field ever claims WhatsApp delivered anything, and `queued`, `submitted`, `sent`, `delivered` and `read` are deliberately 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.'
contact:
name: ChatRail
url: https://www.chatrail.dev
servers:
- url: https://www.chatrail.dev
security:
- apiKey: []
tags:
- name: Messages
description: Sending, delivery state and export.
paths:
/v1/messages/text:
post:
summary: Send a message
description: '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`.'
tags:
- Messages
operationId: post_v1_messages_text
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
connection:
type: string
minLength: 1
maxLength: 40
to:
anyOf:
- type: string
minLength: 5
maxLength: 40
- type: object
properties:
phone:
type: string
minLength: 5
maxLength: 40
required:
- phone
additionalProperties: false
- type: object
properties:
group:
type: string
minLength: 5
maxLength: 40
required:
- group
additionalProperties: false
body:
type: string
maxLength: 4096
media:
type: object
properties:
url:
type: string
maxLength: 2048
format: uri
kind:
type: string
enum:
- image
- video
- audio
- document
mimetype:
type: string
minLength: 3
maxLength: 255
filename:
type: string
minLength: 1
maxLength: 255
required:
- url
additionalProperties: false
location:
type: object
properties:
latitude:
type: number
minimum: -90
maximum: 90
longitude:
type: number
minimum: -180
maximum: 180
title:
type: string
minLength: 1
maxLength: 255
required:
- latitude
- longitude
additionalProperties: false
contact:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 255
phone:
type: string
minLength: 5
maxLength: 40
organization:
type: string
minLength: 1
maxLength: 255
required:
- name
- phone
additionalProperties: false
poll:
type: object
properties:
question:
type: string
minLength: 1
maxLength: 255
options:
minItems: 2
maxItems: 12
type: array
items:
type: string
minLength: 1
maxLength: 100
multiple_answers:
type: boolean
required:
- question
- options
additionalProperties: false
reply_to:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
mentions:
minItems: 1
maxItems: 50
type: array
items:
type: string
minLength: 1
maxLength: 40
link_preview:
type: boolean
context:
type: object
propertyNames:
type: string
additionalProperties: {}
metadata:
type: object
propertyNames:
type: string
additionalProperties: {}
pause_ai:
type: boolean
context_sensitivity:
type: string
enum:
- normal
- sensitive
- restricted
send_at:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
timezone:
type: string
minLength: 1
maxLength: 64
required:
- connection
- to
additionalProperties: false
example:
connection: main
to: '+15551234567'
body: Order CR-2048 has shipped.
context:
order_id: CR-2048
status: dispatched
responses:
'202':
description: Accepted and queued.
'400':
description: The body, recipient or attachment was rejected.
'402':
description: The plan does not include this. The message names what would allow it.
'409':
description: The connection is not ready, or an identical Idempotency-Key is in flight.
'422':
description: The Idempotency-Key was reused with a different body.
'429':
description: Quota or rate limit.
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/messages:
get:
summary: List messages
description: 'Newest first, cursor paginated. Offsets are not offered: they skip and duplicate.'
tags:
- Messages
operationId: get_v1_messages
parameters:
- name: limit
in: query
required: false
schema:
default: 25
type: integer
minimum: 1
maximum: 100
- name: cursor
in: query
required: false
schema:
type: string
maxLength: 200
- name: connection
in: query
required: false
schema:
type: string
minLength: 1
maxLength: 40
- name: status
in: query
required: false
schema:
type: string
enum:
- scheduled
- accepted
- queued
- submitting
- submitted
- sent
- delivered
- read
- failed
- canceled
- name: direction
in: query
required: false
schema:
type: string
enum:
- inbound
- outbound
- name: q
in: query
required: false
schema:
type: string
minLength: 1
maxLength: 120
responses:
'200':
description: A page of messages and a `next_cursor`.
security:
- apiKey:
- messages:read
x-scopes:
- messages:read
/v1/messages/{id}:
get:
summary: Get a message
tags:
- Messages
operationId: get_v1_messages__id_
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Message id.
responses:
'200':
description: The message and its delivery timeline.
'404':
description: No such message.
security:
- apiKey:
- messages:read
x-scopes:
- messages:read
patch:
summary: Edit a message
description: '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.'
tags:
- Messages
operationId: patch_v1_messages__id_
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Message id.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
body:
type: string
maxLength: 4096
context:
type: object
propertyNames:
type: string
additionalProperties: {}
context_sensitivity:
type: string
enum:
- normal
- sensitive
- restricted
additionalProperties: false
example:
body: 'Corrected: viewing is at 3pm, not 2pm.'
context:
property:
address: 14 Mill Lane
viewing: '15:00'
responses:
'200':
description: The message, with `edited_at` set.
'404':
description: No such message.
'409':
description: 'Not editable: received, not a text message, deleted, or past the window.'
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/messages/{id}/cancel:
post:
summary: Cancel a message
description: Only while it has not reached the provider. Once submitted the message exists on somebody's phone and cancelling would be a lie.
tags:
- Messages
operationId: post_v1_messages__id__cancel
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Message id.
responses:
'200':
description: Cancelled.
'404':
description: No such message.
'409':
description: Already handed to the provider.
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/messages/{id}/reactions:
post:
summary: React to a message
description: 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.
tags:
- Messages
operationId: post_v1_messages__id__reactions
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Message id.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
emoji:
type: string
maxLength: 16
required:
- emoji
additionalProperties: false
example:
emoji: 👍
responses:
'200':
description: The message, with `reaction` set.
'404':
description: No such message.
'409':
description: Not possible for this message, or WhatsApp refused.
'422':
description: The connection's engine does not support reactions.
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/messages/{id}/read:
post:
summary: Mark a message read
description: 'Shows blue ticks to the sender of an inbound message. Idempotent: a second call sends nothing.'
tags:
- Messages
operationId: post_v1_messages__id__read
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Message id.
responses:
'200':
description: The message, with `seen_at` set.
'404':
description: No such message.
'409':
description: Only a received message can be marked read.
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/connections/{connection}/presence:
post:
summary: Show typing in a chat
description: 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.
tags:
- Messages
operationId: post_v1_connections__connection__presence
parameters:
- name: connection
in: path
required: true
schema:
type: string
description: Connection slug.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
to:
anyOf:
- type: string
minLength: 5
maxLength: 40
- type: object
properties:
phone:
type: string
minLength: 5
maxLength: 40
required:
- phone
additionalProperties: false
- type: object
properties:
group:
type: string
minLength: 5
maxLength: 40
required:
- group
additionalProperties: false
state:
type: string
enum:
- typing
- recording
- paused
required:
- to
- state
additionalProperties: false
example:
to: '+447700900123'
state: typing
responses:
'204':
description: Shown.
'404':
description: No such connection.
'409':
description: The connection is not ready.
'422':
description: The connection's engine cannot show typing.
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/messages/{id}/revoke:
post:
summary: Delete a message for everyone
description: 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.
tags:
- Messages
operationId: post_v1_messages__id__revoke
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Message id.
responses:
'200':
description: The message, with `deleted_at` set.
'404':
description: No such message.
'409':
description: 'Not deletable: received, already deleted, or past the window.'
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/messages/{id}/reschedule:
post:
summary: Move a scheduled message
tags:
- Messages
operationId: post_v1_messages__id__reschedule
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Message id.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
send_at:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
timezone:
type: string
minLength: 1
maxLength: 64
required:
- send_at
additionalProperties: false
example:
send_at: '2026-10-01T09:00:00Z'
timezone: Europe/London
responses:
'200':
description: Rescheduled.
'409':
description: No longer scheduled.
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
/v1/messages/export:
get:
summary: Export delivery metadata as CSV
description: Streamed row by row. **Message bodies are not included** — this is delivery metadata, not content.
tags:
- Messages
operationId: get_v1_messages_export
parameters:
- name: connection
in: query
required: false
schema:
type: string
minLength: 1
maxLength: 40
- name: status
in: query
required: false
schema:
type: string
minLength: 1
maxLength: 20
- name: direction
in: query
required: false
schema:
type: string
enum:
- inbound
- outbound
- name: days
in: query
required: false
schema:
default: 30
type: integer
minimum: 1
maximum: 365
responses:
'200':
description: A CSV stream.
security:
- apiKey:
- messages:read
x-scopes:
- messages:read
/v1/numbers/check:
get:
summary: Check whether a number is on WhatsApp
description: '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.'
tags:
- Messages
operationId: get_v1_numbers_check
parameters:
- name: phone
in: query
required: true
schema:
type: string
minLength: 5
maxLength: 40
- name: connection
in: query
required: true
schema:
type: string
minLength: 1
maxLength: 40
responses:
'200':
description: 'The answer, or `checked: false`.'
'404':
description: No such connection.
'429':
description: No lookup budget left; `Retry-After` says how long to wait.
security:
- apiKey:
- messages:read
x-scopes:
- messages:read
post:
summary: Check up to 10 numbers
description: '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.'
tags:
- Messages
operationId: post_v1_numbers_check
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
connection:
type: string
minLength: 1
maxLength: 40
phones:
minItems: 1
maxItems: 10
type: array
items:
type: string
minLength: 1
maxLength: 40
required:
- connection
- phones
additionalProperties: false
example:
connection: main
phones:
- '+923001234567'
- '+2348031234567'
responses:
'200':
description: One result per number.
'400':
description: No numbers, or more than 10.
'404':
description: No such connection.
security:
- apiKey:
- messages:read
x-scopes:
- messages:read
/v1/messages/{id}/media:
get:
summary: Download an inbound attachment
description: '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.'
tags:
- Messages
operationId: get_v1_messages__id__media
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Inbound message id.
responses:
'200':
description: The file.
'404':
description: No downloadable media on this message.
'503':
description: This deployment cannot retrieve media from the transport.
security:
- apiKey:
- messages:read
x-scopes:
- messages:read
/v1/messages/{id}/correlation:
post:
summary: Correct which message a reply answers
description: 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.
tags:
- Messages
operationId: post_v1_messages__id__correlation
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Inbound message id.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reply_to_message_id:
anyOf:
- type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
- type: 'null'
required:
- reply_to_message_id
additionalProperties: false
example:
reply_to_message_id: 019bf4a1-0000-7000-8000-000000000000
responses:
'200':
description: The corrected link, and what it overruled.
'400':
description: The target is not an outbound message in the same conversation.
'404':
description: No such message.
'409':
description: Only an inbound message has a correlation to correct.
security:
- apiKey:
- messages:write
x-scopes:
- messages:write
components:
securitySchemes:
apiKey:
type: http
scheme: bearer
description: 'A workspace API key, `cr_live_…`. Server-side only: it carries workspace authority and must never reach a browser.'
session:
type: apiKey
in: cookie
name: chatrail_session
description: Dashboard session cookie. Set by `/v1/auth/verify`.