ChatmaidDevelopers

Messages

Send, list, and inspect WhatsApp messages via API.

Send Message

Current base URL: https://developers-api.chatmaid.net.

POST /v1/messages/send

Send text or media WhatsApp message from a connected phone.

Send Request Contract

ParameterInTypeRequiredRules
fromPhoneIdBodystringYesSender phone, given as its E.164 number (recommended) or its dashboard ID. Must belong to your account.
toBodystringYesE.164 format (example: +15557654321).
contentBodystringNoMax length 4096. Required if mediaUrls is empty.
mediaUrlsBodystring[]NoPublic HTTPS URLs. Required if content is empty.
idempotencyKeyBodystringNoMax length 64. Same key returns original message.

Send Auth and Status Codes

ItemValue
Required scopemessages:send
Alternative scopemessages:write
Success code201
Error codes400, 401, 403, 404, 429

For sk_test_* keys, sandbox simulates synchronous delivery — the response status is already sent. For sk_live_* keys, the response returns pending and transitions to sent (then delivered / read as WhatsApp receipts arrive) or failed — track via webhooks or by polling GET /v1/messages/:messageId.

curl -X POST https://developers-api.chatmaid.net/v1/messages/send \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fromPhoneId": "+15551234567",
    "to": "+15557654321",
    "content": "Order received. We will update you soon."
  }'
const response = await fetch("https://developers-api.chatmaid.net/v1/messages/send", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.CHATMAID_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    fromPhoneId: "+15551234567",
    to: "+15557654321",
    content: "Order received. We will update you soon.",
  }),
});

const payload = await response.json();
import os
import requests

response = requests.post(
    "https://developers-api.chatmaid.net/v1/messages/send",
    headers={
        "Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "fromPhoneId": "+15551234567",
        "to": "+15557654321",
        "content": "Order received. We will update you soon.",
    },
)

payload = response.json()
{
  "success": true,
  "data": {
    "id": "msg_abc123def456",
    "from": "+15551234567",
    "to": "+15557654321",
    "content": "Order received. We will update you soon.",
    "status": "sent",
    "createdAt": "2026-02-06T14:18:33.000Z"
  }
}

List and Read

GET /v1/messages

List messages with pagination and optional filters.

GET /v1/messages/:messageId

Get full message detail and status timestamps.

List Request Contract

ParameterInTypeRequiredRules
phoneNumberIdQuerystringNoFilter by sender phone ID.
statusQuerystringNopending | sent | delivered | read | failed
pageQuerynumberNoInteger >= 1. Default 1.
limitQuerynumberNoInteger 1..100. Default 20.

Results are automatically scoped to the environment of the API key used (sk_live_ or sk_test_).

Get Request Contract

ParameterInTypeRequiredRules
messageIdPathstringYesMessage identifier (msg_*).
Required scope-messages:readYesRequired for both list and get endpoints.
Status codes-200 | 401 | 403 | 404 | 429YesCommon read endpoint responses.
curl -X GET "https://developers-api.chatmaid.net/v1/messages?status=sent&page=1&limit=20" \
  -H "Authorization: Bearer sk_test_xxx"
const params = new URLSearchParams({
  status: "sent",
  page: "1",
  limit: "20",
});

const response = await fetch(
  "https://developers-api.chatmaid.net/v1/messages?" + params.toString(),
  { headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY } }
);

const payload = await response.json();
import os
import requests

response = requests.get(
    "https://developers-api.chatmaid.net/v1/messages",
    headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
    params={"status": "sent", "page": 1, "limit": 20},
)

payload = response.json()
{
  "success": true,
  "data": {
    "data": [
      {
        "id": "msg_abc123def456",
        "to": "+15557654321",
        "status": "sent",
        "createdAt": "2026-02-06T14:18:33.000Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1,
      "totalPages": 1
    }
  }
}
curl -X GET https://developers-api.chatmaid.net/v1/messages/msg_abc123def456 \
  -H "Authorization: Bearer sk_test_xxx"
const response = await fetch(
  "https://developers-api.chatmaid.net/v1/messages/msg_abc123def456",
  { headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY } }
);

const payload = await response.json();
import os
import requests

response = requests.get(
    "https://developers-api.chatmaid.net/v1/messages/msg_abc123def456",
    headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)

payload = response.json()
{
  "success": true,
  "data": {
    "id": "msg_abc123def456",
    "from": "+15551234567",
    "to": "+15557654321",
    "status": "delivered",
    "createdAt": "2026-02-06T14:18:33.000Z",
    "sentAt": "2026-02-06T14:18:34.000Z",
    "deliveredAt": "2026-02-06T14:18:36.000Z",
    "readAt": null,
    "failedAt": null
  }
}

Inbound Messages

Messages received by your connected phone numbers. Inbound messages exist for live connections only — sandbox (sk_test_*) keys always receive an empty list. For real-time processing, subscribe to the message.received webhook event instead of polling.

GET /v1/messages/inbound

List inbound messages with pagination.

GET /v1/messages/inbound/:messageId

Get a single inbound message by its inmsg_* identifier.

Inbound Request Contract

ParameterInTypeRequiredRules
phoneNumberIdQuerystringNoFilter by receiving phone ID.
pageQuerynumberNoInteger >= 1. Default 1.
limitQuerynumberNoInteger 1..100. Default 20.
Required scope-messages:readYesRequired for both endpoints.
curl -X GET "https://developers-api.chatmaid.net/v1/messages/inbound?page=1&limit=20" \
  -H "Authorization: Bearer sk_live_xxx"
{
  "success": true,
  "data": {
    "data": [
      {
        "id": "inmsg_abc123def456",
        "from": "+15557654321",
        "to": "+15551234567",
        "content": "Thanks, got it!",
        "type": "text",
        "isGroup": false,
        "groupId": null,
        "receivedAt": "2026-02-06T14:21:08.000Z",
        "createdAt": "2026-02-06T14:21:09.000Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1,
      "totalPages": 1
    }
  }
}

The type field is one of text, image, video, audio, document, sticker, location, contact, other. For media messages, content carries the caption when present; media payloads themselves are not exposed through the API.

Status Lifecycle

Message states typically progress in this order:

  • pending
  • sent
  • delivered (recipient device acknowledged the message)
  • read (recipient read the message)
  • failed (terminal failure path)

delivered and read are driven by WhatsApp receipts. read may never arrive if the recipient has read receipts disabled, and delivered can be skipped straight to read.

Media and Idempotency Notes

  • Use mediaUrls with public HTTPS links for media messages.
  • Send content + mediaUrls for media with caption.
  • Optionally include idempotencyKey (max 64 characters) when your client retries requests. Keys are scoped per account. Reusing a key returns the original message with its current data.

Dashboard Setup Prerequisite

Phone registration, WhatsApp connection, and API key creation are handled in dashboard UI. At runtime, integration clients can fetch connected phone IDs via /v1/phone-numbers.