Skip to content
Oxtro
Documentation menu

API reference

Messages API

Send text, a file or a saved template from a connected number, then follow the message until it is read.

Send a message

POST/v1/messagesScope messages:send

A request needs connection_id, to, and one of: text, a template, or media. The connection must be in the CONNECTED state.

FieldTypeDescription
connection_idstring, requiredThe WhatsApp connection ID to send from
tostring, requiredRecipient phone number in E.164 international format
typestringOne of: TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, LOCATION, CONTACT. Default: TEXT.
textstringMessage text, or the caption when sending media
contentstringAlias of text
media_urlstringPublic URL of the file to send. Use with a media type.
media_idstringID of a file already uploaded to the workspace, as an alternative to media_url
templateobjectA saved template to render, found by id or by name
template.idstring
template.namestring
template.variablesobject
Text
curl https://api.getoxtro.com/v1/messages \
  -H "Authorization: Bearer $OXTRO_API_KEY" \
  -H "Idempotency-Key: order-4821-confirmation" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "conn_123",
    "to": "+213555123456",
    "text": "Your order ORD-4821 has been confirmed."
  }'
202 Accepted
{
  "id": "msg_123",
  "status": "queued",
  "queued_at": "2026-10-10T09:41:07.000Z"
}

Send an Idempotency-Key header with every send, so that a retry after a timeout does not become a second WhatsApp message.

Send a template

Templates are your own reusable messages with {{variables}}, written in the Oxtro app. They are not Meta-approved templates and need no review. Reference one by name or id and pass a value for every variable. A missing or blank variable is rejected with MISSING_TEMPLATE_VARIABLE and nothing is sent.

Template
curl https://api.getoxtro.com/v1/messages \
  -H "Authorization: Bearer $OXTRO_API_KEY" \
  -H "Idempotency-Key: order-4821-confirmation" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "conn_123",
    "to": "+213555123456",
    "template": {
      "name": "order_confirmed",
      "variables": {
        "customer_name": "Ahmed",
        "order_number": "ORD-4821"
      }
    }
  }'

Send media

Set type to IMAGE, VIDEO, AUDIO or DOCUMENT and give a public media_url. text becomes the caption.

Document with a caption
curl https://api.getoxtro.com/v1/messages \
  -H "Authorization: Bearer $OXTRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "conn_123",
    "to": "+213555123456",
    "type": "DOCUMENT",
    "media_url": "https://example.com/invoices/ORD-4821.pdf",
    "text": "Invoice for order ORD-4821"
  }'

Errors

HTTPCodeWhen
400INVALID_REQUESTA field is missing or malformed, or the request has no text, template or media.
400INVALID_PHONEto is not a valid phone number.
400MISSING_TEMPLATE_VARIABLEThe template has a variable you did not provide.
400CONNECTION_OFFLINEThe connection is not in the CONNECTED state. Scan the QR code again.
403QUOTA_EXCEEDEDThe monthly message limit of your plan is reached.
404NOT_FOUNDThe connection, template or media does not exist in this workspace.
409IDEMPOTENCY_CONFLICTThe Idempotency-Key was already used with a different body.

See Errors for the error format and the codes shared by every endpoint.

Get a message

GET/v1/messages/{id}Scope messages:read

curl
curl https://api.getoxtro.com/v1/messages/msg_123 \
  -H "Authorization: Bearer $OXTRO_API_KEY"
200 OK
{
  "id": "msg_123",
  "workspace_id": "ws_123",
  "connection_id": "conn_123",
  "direction": "OUTBOUND",
  "type": "TEXT",
  "content": "Your order ORD-4821 has been confirmed.",
  "status": "DELIVERED",
  "error_message": null,
  "queued_at": "2026-10-10T09:41:07.000Z",
  "sent_at": "2026-10-10T09:41:08.000Z",
  "delivered_at": "2026-10-10T09:41:10.000Z",
  "read_at": null,
  "failed_at": null,
  "created_at": "2026-10-10T09:41:07.000Z"
}
FieldTypeDescription
idstring, required
workspace_idstring, required
connection_idstring, required
directionstring, requiredOne of: OUTBOUND, INBOUND.
typestring, requiredOne of: TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, LOCATION, CONTACT.
contentstring | null
statusstring, requiredOne of: QUEUED, PROCESSING, SENT, DELIVERED, READ, FAILED, CANCELLED.
error_messagestring | null
queued_atISO 8601 string | null
sent_atISO 8601 string | null
delivered_atISO 8601 string | null
read_atISO 8601 string | null
failed_atISO 8601 string | null
created_atISO 8601 string, required

Statuses

StatusMeaning
QUEUEDAccepted and waiting in the send queue.
PROCESSINGBeing sent.
SENTHanded to WhatsApp. sent_at is set.
DELIVEREDReached the recipient device. delivered_at is set.
READOpened by the recipient, if they share read receipts. read_at is set.
FAILEDCould not be sent after all retries. See error_message.
CANCELLEDCancelled before it was sent.

Polling works, but webhooks tell you about each change as it happens.