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
| Parameter | In | Type | Required | Rules |
|---|---|---|---|---|
fromPhoneId | Body | string | Yes | Sender phone, given as its E.164 number (recommended) or its dashboard ID. Must belong to your account. |
to | Body | string | Yes | E.164 format (example: +15557654321). |
content | Body | string | No | Max length 4096. Required if mediaUrls is empty. |
mediaUrls | Body | string[] | No | Public HTTPS URLs. Required if content is empty. |
idempotencyKey | Body | string | No | Max length 64. Same key returns original message. |
Send Auth and Status Codes
| Item | Value |
|---|---|
| Required scope | messages:send |
| Alternative scope | messages:write |
| Success code | 201 |
| Error codes | 400, 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
| Parameter | In | Type | Required | Rules |
|---|---|---|---|---|
phoneNumberId | Query | string | No | Filter by sender phone ID. |
status | Query | string | No | pending | sent | delivered | read | failed |
page | Query | number | No | Integer >= 1. Default 1. |
limit | Query | number | No | Integer 1..100. Default 20. |
Results are automatically scoped to the environment of the API key used (sk_live_ or sk_test_).
Get Request Contract
| Parameter | In | Type | Required | Rules |
|---|---|---|---|---|
messageId | Path | string | Yes | Message identifier (msg_*). |
| Required scope | - | messages:read | Yes | Required for both list and get endpoints. |
| Status codes | - | 200 | 401 | 403 | 404 | 429 | Yes | Common 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
| Parameter | In | Type | Required | Rules |
|---|---|---|---|---|
phoneNumberId | Query | string | No | Filter by receiving phone ID. |
page | Query | number | No | Integer >= 1. Default 1. |
limit | Query | number | No | Integer 1..100. Default 20. |
| Required scope | - | messages:read | Yes | Required 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:
pendingsentdelivered(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
mediaUrlswith public HTTPS links for media messages. - Send
content+mediaUrlsfor 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.