# Chatmaid Developers — Full Documentation
> WhatsApp messaging API for developers and AI agents. This is the complete concatenated documentation.
---
# Account
Source: https://developers.chatmaid.net/docs/account
Description: Read account profile and usage data via API key.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Overview
Account metadata and usage are available via standard API-key endpoints:
- `GET /v1/account`
- `GET /v1/account/usage`
Integration host: `https://developers-api.chatmaid.net`.
## Get Account
### GET /v1/account
Get account profile for the API key owner.
### Get Account Auth and Status Codes
| Item | Value |
| --- | --- |
| Recommended scope | `account:read` |
| Also accepted scopes | `messages:read, messages:send, messages:write` |
| Success code | `200` |
| Error codes | `401, 403, 429` |
```bash
curl -X GET https://developers-api.chatmaid.net/v1/account \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/account", {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/account",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"accountId": "acc_abc123def456",
"name": "Acme Inc",
"email": "developer@acme.com",
"subscriptionStatus": "active",
"stats": {
"apiKeys": 2,
"phoneNumbers": 1,
"messages": 328
}
}
}
```
## Usage
### GET /v1/account/usage
Get account usage metrics for day, week, or month.
### Usage Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `period` | Query | `string` | No | day \| week \| month, default month |
| Required scope | - | `account:read` | No | messages:* scopes are also accepted. |
| Status codes | - | `200 \| 401 \| 403 \| 429` | Yes | Typical usage endpoint responses. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/account/usage?period=month" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/account/usage?period=month", {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/account/usage",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
params={"period": "month"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"period": "month",
"startDate": "2026-01-07T14:20:00.000Z",
"endDate": "2026-02-06T14:20:00.000Z",
"messages": {
"total": 328,
"pending": 0,
"sent": 314,
"failed": 14
},
"apiRequests": {
"total": 1242,
"successful": 1221,
"failed": 21
}
}
}
```
---
# API Reference
Source: https://developers.chatmaid.net/docs/api-reference
Description: Endpoint-level parameters and response fields for integration APIs.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
Base URL:
- `https://developers-api.chatmaid.net`
Integration host: `https://developers-api.chatmaid.net`.
## POST /v1/messages/send
### POST /v1/messages/send
Create a new outbound message.
### Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `fromPhoneId` | Body | `string` | Yes | Must be a phone ID in your account. |
| `to` | Body | `string` | Yes | E.164 format (example: +15551234567). |
| `content` | Body | `string` | No | Max length 4096. Required if mediaUrls is empty. |
| `mediaUrls` | Body | `string[]` | No | Public HTTPS URLs only. Required if content is empty. |
| `idempotencyKey` | Body | `string` | No | Max length 64. Reuse returns the original message. |
### Authorization and Scopes
| Requirement | Value |
| --- | --- |
| Auth header | `Authorization: Bearer sk_test_* \| sk_live_*` |
| Any required scope | `messages:send` |
| Alternative accepted scope | `messages:write` |
### Status Codes
| Code | Meaning |
| --- | --- |
| `201` | Message accepted. |
| `400` | Validation error or phone not connected for live key. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `404` | fromPhoneId not found in account. |
| `429` | Rate limit exceeded for this API key. |
### Response Fields
| Field | Type | Notes |
| --- | --- | --- |
| `data.id` | `string` | Message ID (msg_*). |
| `data.environment` | `"test" \| "live"` | Inferred from API key prefix. |
| `data.status` | `pending\|sent\|failed` | Status after a successful send is sent. Failed sends are marked failed. |
| `data.sentAt` | `string \| null` | Set when message is marked sent. |
| `data.errorCode` | `string \| null` | Populated when status is failed. |
```bash
curl -X POST https://developers-api.chatmaid.net/v1/messages/send \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{"fromPhoneId":"507f1f77bcf86cd799439011","to":"+15557654321","content":"Hello"}'
```
```ts
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: "507f1f77bcf86cd799439011",
to: "+15557654321",
content: "Hello",
}),
});
const payload = await response.json();
```
```python
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": "507f1f77bcf86cd799439011",
"to": "+15557654321",
"content": "Hello",
},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "msg_abc123def456",
"from": "+15551234567",
"to": "+15557654321",
"content": "Hello",
"mediaUrls": [],
"environment": "test",
"status": "sent",
"createdAt": "2026-02-06T14:18:33.000Z",
"sentAt": "2026-02-06T14:18:33.120Z",
"failedAt": null,
"errorCode": null,
"errorMessage": null
}
}
```
For live keys, the response is returned with `status: "pending"` and is updated to `sent` or `failed` once the WhatsApp microservice confirms delivery (track via webhooks or by polling `GET /v1/messages/:messageId`).
## GET /v1/messages
### GET /v1/messages
Paginated list of messages for the authenticated account.
### Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `phoneNumberId` | Query | `string` | No | Filter by sender phone ID. |
| `status` | Query | `string` | No | One of pending, sent, failed. |
| `environment` | Query | `string` | No | One of live, test. Defaults to API key environment. |
| `page` | Query | `number` | No | Integer >= 1. Default 1. |
| `limit` | Query | `number` | No | Integer 1..100. Default 20. |
### Authorization and Scopes
| Requirement | Value |
| --- | --- |
| Auth header | `Authorization: Bearer sk_test_* \| sk_live_*` |
| Required scope | `messages:read` |
### Status Codes
| Code | Meaning |
| --- | --- |
| `200` | Page returned. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `429` | Rate limit exceeded for this API key. |
### Response Fields
| Field | Type | Notes |
| --- | --- | --- |
| `data.data[]` | `Message[]` | Array sorted by createdAt desc. |
| `data.pagination.page` | `number` | Current page index (1-based). |
| `data.pagination.limit` | `number` | Requested page size. |
| `data.pagination.total` | `number` | Total matching messages. |
| `data.pagination.totalPages` | `number` | Computed page count. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/messages?status=sent&page=1&limit=20" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const query = new URLSearchParams({
status: "sent",
page: "1",
limit: "20",
});
const response = await fetch(
"https://developers-api.chatmaid.net/v1/messages?" + query.toString(),
{ headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY } }
);
const payload = await response.json();
```
```python
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()
```
```json
{
"success": true,
"data": {
"data": [
{
"id": "msg_abc123def456",
"from": "+15551234567",
"to": "+15557654321",
"content": "Hello",
"status": "sent",
"createdAt": "2026-02-06T14:18:33.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}
}
```
## GET /v1/messages/:messageId
### GET /v1/messages/:messageId
Get one message and its lifecycle timestamps.
### Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `messageId` | Path | `string` | Yes | Message ID returned by send endpoint (msg_*). |
### Authorization and Scopes
| Requirement | Value |
| --- | --- |
| Auth header | `Authorization: Bearer sk_test_* \| sk_live_*` |
| Required scope | `messages:read` |
### Status Codes
| Code | Meaning |
| --- | --- |
| `200` | Message returned. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `404` | Message ID not found in this account. |
| `429` | Rate limit exceeded for this API key. |
```bash
curl -X GET https://developers-api.chatmaid.net/v1/messages/msg_abc123def456 \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
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();
```
```python
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()
```
```json
{
"success": true,
"data": {
"id": "msg_abc123def456",
"from": "+15551234567",
"to": "+15557654321",
"content": "Hello",
"mediaUrls": [],
"environment": "test",
"status": "sent",
"errorCode": null,
"errorMessage": null,
"createdAt": "2026-02-06T14:18:33.000Z",
"sentAt": "2026-02-06T14:18:33.120Z",
"failedAt": null
}
}
```
## GET /v1/phone-numbers
### GET /v1/phone-numbers
List phone numbers available to this account.
### Authorization and Scopes
| Requirement | Value |
| --- | --- |
| Auth header | `Authorization: Bearer sk_test_* \| sk_live_*` |
| Recommended scope | `phone_numbers:read` |
| Backward-compatible scope accepted | `phone-numbers:read` |
### Status Codes
| Code | Meaning |
| --- | --- |
| `200` | Phone list returned. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `429` | Rate limit exceeded for this API key. |
```bash
curl -X GET https://developers-api.chatmaid.net/v1/phone-numbers \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/phone-numbers", {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/phone-numbers",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": [
{
"id": "507f1f77bcf86cd799439011",
"phoneNumber": "+15551234567",
"displayName": "Primary Sender",
"connectionStatus": "connected",
"lastConnectedAt": "2026-02-06T14:10:00.000Z",
"lastDisconnectedAt": null,
"createdAt": "2026-02-01T10:20:00.000Z",
"updatedAt": "2026-02-06T14:10:00.000Z"
}
]
}
```
## GET /v1/phone-numbers/:id
### GET /v1/phone-numbers/:id
Get one phone number by internal ID or URL-encoded E.164 value.
### Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `id` | Path | `string` | Yes | Accepts phone ID or URL-encoded E.164 value (example: %2B15551234567). |
### Status Codes
| Code | Meaning |
| --- | --- |
| `200` | Phone returned. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `404` | Phone reference not found in this account. |
| `429` | Rate limit exceeded for this API key. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/phone-numbers/%2B15551234567" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const phoneRef = encodeURIComponent("+15551234567");
const response = await fetch(`https://developers-api.chatmaid.net/v1/phone-numbers/${phoneRef}`, {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
from urllib.parse import quote
phone_ref = quote("+15551234567", safe="")
response = requests.get(
f"https://developers-api.chatmaid.net/v1/phone-numbers/{phone_ref}",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"phoneNumber": "+15551234567",
"displayName": "Primary Sender",
"connectionStatus": "connected",
"lastConnectedAt": "2026-02-06T14:10:00.000Z",
"lastDisconnectedAt": null,
"createdAt": "2026-02-01T10:20:00.000Z",
"updatedAt": "2026-02-06T14:10:00.000Z"
}
}
```
## GET /v1/phone-numbers/:id/status
### GET /v1/phone-numbers/:id/status
Get live connection state by internal ID or URL-encoded E.164 value.
### Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `id` | Path | `string` | Yes | Accepts phone ID or URL-encoded E.164 value (example: %2B15551234567). |
### Status Codes
| Code | Meaning |
| --- | --- |
| `200` | Status returned. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `404` | Phone reference not found in this account. |
| `429` | Rate limit exceeded for this API key. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/phone-numbers/%2B15551234567/status" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const phoneRef = encodeURIComponent("+15551234567");
const response = await fetch(`https://developers-api.chatmaid.net/v1/phone-numbers/${phoneRef}/status`, {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
from urllib.parse import quote
phone_ref = quote("+15551234567", safe="")
response = requests.get(
f"https://developers-api.chatmaid.net/v1/phone-numbers/{phone_ref}/status",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"phoneNumber": "+15551234567",
"connectionStatus": "connected",
"lastConnectedAt": "2026-02-06T14:10:00.000Z",
"lastDisconnectedAt": null,
"updatedAt": "2026-02-06T14:10:00.000Z"
}
}
```
## GET /v1/account
### GET /v1/account
Read account profile and aggregate counters.
### Authorization and Scopes
| Requirement | Value |
| --- | --- |
| Auth header | `Authorization: Bearer sk_test_* \| sk_live_*` |
| Recommended scope | `account:read` |
| Other accepted scopes | `messages:read, messages:send, messages:write` |
### Status Codes
| Code | Meaning |
| --- | --- |
| `200` | Account data returned. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `429` | Rate limit exceeded for this API key. |
```bash
curl -X GET https://developers-api.chatmaid.net/v1/account \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/account", {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/account",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"accountId": "acc_abc123def456",
"name": "Acme Inc",
"email": "developer@acme.com",
"subscriptionStatus": "active",
"stats": {
"apiKeys": 2,
"phoneNumbers": 1,
"messages": 328
}
}
}
```
## GET /v1/account/usage
### GET /v1/account/usage
Read account usage metrics for day, week, or month.
### Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `period` | Query | `string` | No | One of day, week, month. Default month. |
### Status Codes
| Code | Meaning |
| --- | --- |
| `200` | Usage returned. |
| `401` | Missing/invalid/expired API key. |
| `403` | API key does not include required scope. |
| `429` | Rate limit exceeded for this API key. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/account/usage?period=month" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/account/usage?period=month", {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/account/usage",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
params={"period": "month"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"period": "month",
"startDate": "2026-01-07T14:20:00.000Z",
"endDate": "2026-02-06T14:20:00.000Z",
"messages": {
"total": 328,
"pending": 0,
"sent": 314,
"failed": 14
},
"apiRequests": {
"total": 1242,
"successful": 1221,
"failed": 21
}
}
}
```
## Response Schema
```json
{
"success": true,
"data": {}
}
```
```json
{
"success": false,
"error": "Unauthorized",
"message": ["Invalid API key"],
"statusCode": 401,
"timestamp": "2026-02-06T14:22:30.000Z",
"path": "/v1/messages"
}
```
```json
{
"success": false,
"error": "Rate limit exceeded",
"retryAfter": 60
}
```
---
# Authentication
Source: https://developers.chatmaid.net/docs/authentication
Description: API key authentication and environment behavior.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## API Key Model
Integration endpoints use API key authentication with Bearer tokens.
- **Test keys** start with `sk_test_` and simulate delivery.
- **Live keys** start with `sk_live_` and send real WhatsApp traffic. Requires an active subscription.
### Key Prefix Behavior
| Prefix | Behavior | Host |
| --- | --- | --- |
| `sk_test_*` | Sandbox behavior (simulated delivery lifecycle) | `https://developers-api.chatmaid.net` |
| `sk_live_*` | Production behavior (real WhatsApp delivery) | `https://developers-api.chatmaid.net` |
## Key Format and Use
### POST /v1/messages/send
Representative protected endpoint using API key Bearer auth.
```bash
curl -X GET https://developers-api.chatmaid.net/v1/messages \
-H "Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/messages", {
headers: {
Authorization: "Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxx",
},
});
```
```python
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/messages",
headers={"Authorization": "Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxx"},
)
```
```json
{
"success": false,
"error": "Unauthorized",
"message": ["Invalid API key"],
"statusCode": 401,
"timestamp": "2026-02-06T14:22:30.000Z",
"path": "/v1/messages"
}
```
## Scopes and Limits
Common scopes:
- `messages:send` (or `messages:write`)
- `messages:read`
- `phone_numbers:read` (optional, phone-number endpoints)
- `account:read` (optional, account endpoints)
Rate limit is enforced per API key. Default is 100 requests per minute unless account-specific limits are configured.
Environment mapping:
- Host: `https://developers-api.chatmaid.net` for both environments
- `sk_test_*` -> sandbox behavior
- `sk_live_*` -> production behavior
Environment is inferred from key prefix; do not pass environment as request input.
Integration host: `https://developers-api.chatmaid.net`.
### Endpoint to Scope Matrix
| Endpoint | Required Scope | Also Accepted |
| --- | --- | --- |
| `POST /v1/messages/send` | `messages:send` | `messages:write` |
| `GET /v1/messages` | `messages:read` | - |
| `GET /v1/messages/:messageId` | `messages:read` | - |
| `GET /v1/phone-numbers*` | `phone_numbers:read` | `phone-numbers:read, messages:*` |
| `GET /v1/account*` | `account:read` | `messages:read, messages:send, messages:write` |
---
# Cookbook
Source: https://developers.chatmaid.net/docs/cookbook
Description: End-to-end implementation workflow from setup to production.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';
## Architecture
This cookbook covers the full runtime loop for an integration service using API-key auth only:
1. Load API key and set a single base URL.
2. Fetch connected sender phone numbers.
3. Send outbound message requests.
4. Track status via polling and optional webhooks.
5. Apply retry/idempotency strategy and promote to production.
Integration host: `https://developers-api.chatmaid.net`.
## Bootstrap Client
Standardize your HTTP client once and reuse it for all endpoints.
```bash
export CHATMAID_BASE_URL="https://developers-api.chatmaid.net"
export CHATMAID_API_KEY="sk_test_xxx"
curl -X GET "$CHATMAID_BASE_URL/v1/account" \
-H "Authorization: Bearer $CHATMAID_API_KEY"
```
```ts
const chatmaid = {
baseUrl: process.env.CHATMAID_BASE_URL || "https://developers-api.chatmaid.net",
apiKey: process.env.CHATMAID_API_KEY!,
async request(path: string, init: RequestInit = {}) {
const response = await fetch(this.baseUrl + path, {
...init,
headers: {
Authorization: "Bearer " + this.apiKey,
"Content-Type": "application/json",
...(init.headers || {}),
},
});
return response.json();
},
};
```
```python
import os
import requests
class ChatmaidClient:
def __init__(self):
self.base_url = os.getenv("CHATMAID_BASE_URL", "https://developers-api.chatmaid.net")
self.api_key = os.environ["CHATMAID_API_KEY"]
def request(self, method: str, path: str, **kwargs):
headers = kwargs.pop("headers", {})
headers.update({
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
})
response = requests.request(method, self.base_url + path, headers=headers, **kwargs)
return response.json()
```
## Send and Track
Use this operational sequence inside your application flow.
### 1) Resolve Sender Phone ID
### GET /v1/phone-numbers
Read available phone numbers and select a connected sender.
```bash
curl -X GET https://developers-api.chatmaid.net/v1/phone-numbers \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const phones = await chatmaid.request("/v1/phone-numbers");
const connected = phones.data.find((phone: any) => phone.connectionStatus === "connected");
```
```python
phones = client.request("GET", "/v1/phone-numbers")
connected = next((p for p in phones["data"] if p["connectionStatus"] == "connected"), None)
```
### 2) Send Message
### POST /v1/messages/send
Create outbound message. Use optional idempotency key only for retry-heavy workflows.
```bash
curl -X POST https://developers-api.chatmaid.net/v1/messages/send \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"fromPhoneId": "507f1f77bcf86cd799439011",
"to": "+15557654321",
"content": "Order 7784 confirmed"
}'
```
```ts
const sent = await chatmaid.request("/v1/messages/send", {
method: "POST",
body: JSON.stringify({
fromPhoneId: "507f1f77bcf86cd799439011",
to: "+15557654321",
content: "Order 7784 confirmed",
}),
});
const messageId = sent.data.id;
```
```python
sent = client.request(
"POST",
"/v1/messages/send",
json={
"fromPhoneId": "507f1f77bcf86cd799439011",
"to": "+15557654321",
"content": "Order 7784 confirmed",
},
)
message_id = sent["data"]["id"]
```
```json
{
"success": true,
"data": {
"id": "msg_abc123def456",
"status": "sent",
"environment": "test",
"createdAt": "2026-02-06T14:18:33.000Z"
}
}
```
### 3) Poll Status Until Terminal
### GET /v1/messages/:messageId
Poll until status reaches sent or failed.
```bash
curl -X GET https://developers-api.chatmaid.net/v1/messages/msg_abc123def456 \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
async function waitForTerminal(messageId: string) {
while (true) {
const current = await chatmaid.request("/v1/messages/" + messageId);
const status = current.data.status;
if (["sent", "failed"].includes(status)) return current;
await new Promise((resolve) => setTimeout(resolve, 1500));
}
}
```
```python
import time
def wait_for_terminal(message_id: str):
while True:
current = client.request("GET", f"/v1/messages/{message_id}")
status = current["data"]["status"]
if status in ["sent", "failed"]:
return current
time.sleep(1.5)
```
## Reliability Patterns
- Treat `messageId` as your canonical correlation key across systems.
- Use webhook events for push updates; keep polling as fallback.
- Use `idempotencyKey` only when your retry policy can repeat sends.
- Capture and alert on `failed` statuses with retry in your domain logic.
Retry only transport failures (timeout/network/5xx) with exponential backoff. Do not blindly retry business errors (4xx) without fixing request payload or permissions.
## Go Live Runbook
1. Keep the same API host and verify all examples/routes still match your code.
2. Swap credentials to `sk_live_*` key in production secret store.
3. Validate connected production phone status via `GET /v1/phone-numbers/:id/status`.
4. Send a low-risk real message and verify status progression.
5. Enable webhook verification and alerting for failed deliveries.
---
# Environments
Source: https://developers.chatmaid.net/docs/environments
Description: Single-host model, key-based environment behavior, and go-live path.
import { Callout } from 'fumadocs-ui/components/callout';
## Base URL
- `https://developers-api.chatmaid.net`
All integration endpoints are under `/v1`, for example: `https://developers-api.chatmaid.net/v1/messages/send`.
Integration host: `https://developers-api.chatmaid.net`.
## Behavior Differences
- Use `sk_test_*` keys for sandbox behavior and `sk_live_*` keys for production behavior.
- Environment is inferred from API key prefix. Do not send environment in request body or query.
- Sandbox simulates message delivery status progression. Test keys work without a subscription.
- Production sends real WhatsApp messages via connected numbers. **Live keys require an active subscription.**
- Webhook payload structure is identical across both environments.
Never reuse production API keys in sandbox or test keys in production. Keep keys and webhook endpoints separated by environment.
## Go-Live Checklist
1. Validate full flow using `sk_test_*` keys.
2. Keep the same API host and switch credentials to `sk_live_*` keys.
3. Point webhooks to production receiver endpoints and verify signatures.
4. Monitor initial traffic for rate limits and delivery failures.
---
# Errors
Source: https://developers.chatmaid.net/docs/errors
Description: Error response format and common troubleshooting paths.
import { Callout } from 'fumadocs-ui/components/callout';
## Error Envelope
Most API errors follow this envelope.
```json
{
"success": false,
"error": "Unauthorized",
"message": ["Invalid API key"],
"statusCode": 401,
"timestamp": "2026-02-06T14:22:30.000Z",
"path": "/v1/messages/send"
}
```
Validation errors return the same shape, with multiple entries in `message`.
```json
{
"success": false,
"error": "Bad Request",
"message": [
"Phone number must be in E.164 format (e.g., +1987654321)",
"to must be a string"
],
"statusCode": 400,
"timestamp": "2026-02-06T14:22:30.000Z",
"path": "/v1/messages/send"
}
```
## Common Errors
### Common Error Matrix
| Code | Cause | Typical Fix |
| --- | --- | --- |
| `400` | Invalid payload or query values | Fix request format, required fields, or value constraints. |
| `401` | Missing, invalid, or expired API key | Send a valid Bearer API key from dashboard. |
| `403` | Missing scope or inactive subscription | Use a key with correct scope and ensure account is active. |
| `404` | Unknown message/phone resource | Verify identifiers belong to your account. |
| `429` | Per-key rate limit exceeded | Back off and retry after retryAfter seconds. |
| `500` | Unexpected internal error | Retry safely and contact support with messageId/request context. |
```json
{
"success": false,
"error": "Rate limit exceeded",
"retryAfter": 60
}
```
## Debug Workflow
1. Verify `Authorization: Bearer sk_live_*` or `sk_test_*` header format.
2. Check key scopes include required permissions (for phone endpoints, include `phone_numbers:read`).
3. Confirm sender phone is connected in dashboard.
4. If using phone number in path, URL-encode it (for example `%2B15551234567`).
5. Use idempotency keys and inspect message status endpoint.
Log `messageId` values from send responses and use them as the canonical correlation key across polling, webhook processing, and support tickets.
---
# Getting Started
Source: https://developers.chatmaid.net/docs/getting-started
Description: End-to-end quickstart from account creation to first message.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';
## Prerequisites
- Account setup completed in Chatmaid dashboard
- At least one connected sender phone in dashboard
- API key generated in dashboard with message scopes
## First Message Flow
1. Store your API key in your server secrets.
2. Fetch available sender phone IDs from standard API endpoints.
3. Send a message with API key auth.
Current base URL: `https://developers-api.chatmaid.net`.
```bash
curl -X GET https://developers-api.chatmaid.net/v1/phone-numbers \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/phone-numbers", {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const phones = await response.json();
```
```python
import os
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/phone-numbers",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
phones = response.json()
```
4. Send your first message.
```bash
curl -X POST https://developers-api.chatmaid.net/v1/messages/send \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"fromPhoneId": "507f1f77bcf86cd799439011",
"to": "+15557654321",
"content": "Hello from Chatmaid"
}'
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/messages/send", {
method: "POST",
headers: {
"Authorization": "Bearer sk_test_xxx",
"Content-Type": "application/json"
},
body: JSON.stringify({
fromPhoneId: "507f1f77bcf86cd799439011",
to: "+15557654321",
content: "Hello from Chatmaid"
})
});
const payload = await response.json();
```
```python
import requests
response = requests.post(
"https://developers-api.chatmaid.net/v1/messages/send",
headers={
"Authorization": "Bearer sk_test_xxx",
"Content-Type": "application/json",
},
json={
"fromPhoneId": "507f1f77bcf86cd799439011",
"to": "+15557654321",
"content": "Hello from Chatmaid",
},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "msg_abc123def456",
"from": "+15551234567",
"to": "+15557654321",
"content": "Hello from Chatmaid",
"mediaUrls": [],
"environment": "test",
"status": "sent",
"createdAt": "2026-02-06T14:18:33.000Z",
"sentAt": "2026-02-06T14:18:33.120Z",
"failedAt": null,
"errorCode": null,
"errorMessage": null
}
}
```
5. Poll status until terminal state.
```bash
curl -X GET https://developers-api.chatmaid.net/v1/messages/msg_abc123def456 \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch(
"https://developers-api.chatmaid.net/v1/messages/msg_abc123def456",
{ headers: { "Authorization": "Bearer sk_test_xxx" } }
);
const payload = await response.json();
```
```python
import requests
response = requests.get(
"https://developers-api.chatmaid.net/v1/messages/msg_abc123def456",
headers={"Authorization": "Bearer sk_test_xxx"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "msg_abc123def456",
"status": "sent",
"environment": "test",
"sentAt": "2026-02-06T14:18:34.000Z",
"failedAt": null,
"errorCode": null,
"errorMessage": null
}
}
```
If your app retries failed HTTP requests aggressively, include an `idempotencyKey` generated from your own operation ID to avoid duplicate sends.
## Production Checklist
- Keep the same base URL and switch to `sk_live_*` credentials in production.
- Use `sk_live_*` keys only in production systems.
- Configure webhook destination in dashboard and verify signatures in your receiver.
- Monitor failed delivery events and implement retries in your system.
---
# Overview
Source: https://developers.chatmaid.net/docs
Description: Introduction to the Chatmaid Developers platform and API contract.
import { Callout } from 'fumadocs-ui/components/callout';
## Platform
Chatmaid Developers provides a WhatsApp messaging API plus a dashboard for key management, billing, phone connections, and delivery observability.
This documentation focuses on the integration contract used by API consumers. Public APIs are versioned and stable under `/v1`.
Base URL:
- `https://developers-api.chatmaid.net`
Integration host: `https://developers-api.chatmaid.net`.
Integrators should only call documented message endpoints. Dashboard setup actions (billing, phone setup, webhook configuration) happen in the UI. This docs site focuses on API-key authenticated endpoints for runtime integrations.
## API Contract
Integration endpoints for API consumers:
- `POST /v1/messages/send`
- `GET /v1/messages`
- `GET /v1/messages/:messageId`
- `GET /v1/phone-numbers`
- `GET /v1/phone-numbers/:id`
- `GET /v1/phone-numbers/:id/status`
- `GET /v1/account`
- `GET /v1/account/usage`
## Integration Flow
1. Create account and complete setup in dashboard.
2. Develop and validate first with `sk_test_*` keys.
3. Generate API key in dashboard.
4. Discover phone IDs via `GET /v1/phone-numbers`.
5. Send messages via `POST /v1/messages/send`.
6. Track status via polling or webhooks.
7. Promote by using `sk_live_*` keys in production systems.
---
# Build with AI agents
Source: https://developers.chatmaid.net/docs/mcp
Description: Two ways to make Claude Code, Cursor, Windsurf, and Claude Desktop send WhatsApp messages: point them at our markdown docs, or install the Chatmaid MCP server.
import { Callout } from "fumadocs-ui/components/callout";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
There are two ways to give an AI agent the ability to send WhatsApp through Chatmaid. Pick whichever fits your tooling — they're complementary.
## 1. Point your agent at the docs (recommended start)
Every agent that can read a URL can use Chatmaid in seconds. We publish the entire developer site as plain markdown following the [llms.txt](https://llmstxt.org) standard, so you don't have to copy-paste examples.
```bash
curl https://developers.chatmaid.net/llms-full.txt
```
The complete concatenated documentation in one file. Pipe it into your agent's context, attach it to a Claude Project, or stream it via stdin. This is what most agents need.
```bash
curl https://developers.chatmaid.net/llms.txt
```
A lightweight index linking to every page's markdown source. Ideal for agents that fetch selectively rather than loading everything at once.
```bash
curl https://developers.chatmaid.net/raw-docs/getting-started
```
Prefix any docs slug with `/raw-docs/` to get the raw markdown source for that page.
Chatmaid's REST API is small and orthogonal — eight endpoints under `/v1`. Once an
agent has the docs, it can compose any flow you describe in natural language.
### Example prompt
> "Read https://developers.chatmaid.net/llms-full.txt, then send a WhatsApp from my business number to +14155551234 saying the order shipped. Use my key in `CHATMAID_API_KEY`."
## 2. Install the Chatmaid MCP server
If you'd rather give the agent first-class tool calls (structured arguments, autocomplete in some clients, no parsing of curl output), install the MCP server. It's a thin wrapper around the same REST API.
MCP is the open standard Anthropic introduced for AI agents to call external
tools. It's supported by Claude Code, Claude Desktop, Cursor, Windsurf, Zed,
and many other clients.
### Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"chatmaid": {
"command": "npx",
"args": ["-y", "@chatmaid/mcp"],
"env": {
"CHATMAID_API_KEY": "sk_test_xxx_or_sk_live_xxx"
}
}
}
}
```
Restart Claude Desktop. You should see "chatmaid" in the connected-MCP list.
### Cursor
Edit `~/.cursor/mcp.json` (create the file if missing):
```json
{
"mcpServers": {
"chatmaid": {
"command": "npx",
"args": ["-y", "@chatmaid/mcp"],
"env": { "CHATMAID_API_KEY": "sk_test_xxx_or_sk_live_xxx" }
}
}
}
```
### Claude Code
From any terminal inside your project:
```bash
claude mcp add chatmaid \
--env CHATMAID_API_KEY=sk_test_xxx \
-- npx -y @chatmaid/mcp
```
### Windsurf, Zed, other clients
Any MCP-compatible client can use the same `npx -y @chatmaid/mcp` command. Set `CHATMAID_API_KEY` in the client's environment configuration.
### Available tools
Once connected, the agent can call these tools automatically:
| Tool | What it does |
| -------------------- | ----------------------------------------------------------------------- |
| `send_message` | Send a WhatsApp message — `fromPhoneId` (discovered via `list_phone_numbers`), `to` in E.164, plus `content` and/or `mediaUrls`, optional `idempotencyKey`. |
| `list_messages` | List recent messages with optional `status` / `phoneNumberId` filter and `page` / `limit` pagination. |
| `get_message` | Fetch a single message and its final delivery status. |
| `list_phone_numbers` | Show all phone numbers registered to the current account. |
| `get_phone_number` | Get details for one phone number (accepts internal ID or E.164). |
| `get_phone_status` | Check whether a phone number is currently connected to WhatsApp (accepts internal ID or E.164). |
| `get_account` | Account profile (accountId, name, email, subscription status). |
| `get_usage` | Usage stats for `period` = `day` \| `week` \| `month` (defaults to month). |
The MCP server is open-source at [github.com/chatmaid/mcp](https://github.com/chatmaid/mcp). PRs welcome.
## Get an API key
1. Sign in at [developers.chatmaid.net/dashboard](https://developers.chatmaid.net/dashboard).
2. Go to **API Keys** and generate a `sk_test_*` key.
3. Paste it into your agent's environment.
While an agent is learning, use a `sk_test_*` key. Messages sent with test
keys run through Chatmaid's sandbox — nothing reaches real WhatsApp numbers.
Promote to `sk_live_*` only after you've confirmed behavior.
## Why the API is agent-friendly
Chatmaid's HTTP surface is built for autonomous use, regardless of which approach above you pick:
- **Idempotency keys** — safe retries after a timeout or tool-call interruption.
- **Structured error envelopes** — errors return a consistent `{ success, error, message[], statusCode }` shape, so agents know whether to retry, swap inputs, or escalate.
- **Sandbox keys** — `sk_test_*` simulates the full delivery lifecycle without touching WhatsApp.
- **Predictable status lifecycle** — messages go `pending → sent | failed`, so an agent can poll confidently.
See the [API Reference](/docs/api-reference) for the full contract and the [Cookbook](/docs/cookbook) for end-to-end agent integration patterns.
---
# Messages
Source: https://developers.chatmaid.net/docs/messages
Description: Send, list, and inspect WhatsApp messages via API.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';
## 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 | 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` or `failed` once WhatsApp confirms — track via webhooks or by polling `GET /v1/messages/:messageId`.
```bash
curl -X POST https://developers-api.chatmaid.net/v1/messages/send \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"fromPhoneId": "507f1f77bcf86cd799439011",
"to": "+15557654321",
"content": "Order received. We will update you soon."
}'
```
```ts
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: "507f1f77bcf86cd799439011",
to: "+15557654321",
content: "Order received. We will update you soon.",
}),
});
const payload = await response.json();
```
```python
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": "507f1f77bcf86cd799439011",
"to": "+15557654321",
"content": "Order received. We will update you soon.",
},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "msg_abc123def456",
"from": "+15551234567",
"to": "+15557654321",
"content": "Order received. We will update you soon.",
"environment": "test",
"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 \| failed |
| `environment` | Query | `string` | No | live \| test. Defaults to API key environment. |
| `page` | Query | `number` | No | Integer >= 1. Default 1. |
| `limit` | Query | `number` | No | Integer 1..100. Default 20. |
### 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. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/messages?status=sent&page=1&limit=20" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
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();
```
```python
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()
```
```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
}
}
}
```
```bash
curl -X GET https://developers-api.chatmaid.net/v1/messages/msg_abc123def456 \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
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();
```
```python
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()
```
```json
{
"success": true,
"data": {
"id": "msg_abc123def456",
"from": "+15551234567",
"to": "+15557654321",
"status": "sent",
"environment": "test",
"createdAt": "2026-02-06T14:18:33.000Z",
"sentAt": "2026-02-06T14:18:34.000Z",
"failedAt": null
}
}
```
## Status Lifecycle
Message states typically progress in this order:
- `pending`
- `sent`
- `failed` (terminal failure path)
### 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.
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`.
---
# Phone Numbers
Source: https://developers.chatmaid.net/docs/phone-numbers
Description: List connected phone numbers and inspect connection status.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';
## Overview
Phone number data is available through standard API-key endpoints. Environment is inferred from your API key prefix on a single host.
- `GET /v1/phone-numbers`
- `GET /v1/phone-numbers/:id`
- `GET /v1/phone-numbers/:id/status`
The `:id` path parameter accepts either the internal phone ID or an E.164 phone number. When using a phone number in path, URL-encode it (for example `%2B15551234567`).
Integration host: `https://developers-api.chatmaid.net`.
## List Numbers
### GET /v1/phone-numbers
List all phone numbers in the account that owns the API key.
### List Auth and Status Codes
| Item | Value |
| --- | --- |
| Recommended scope | `phone_numbers:read` |
| Backward-compatible scope | `phone-numbers:read` |
| Success code | `200` |
| Error codes | `401, 403, 429` |
```bash
curl -X GET https://developers-api.chatmaid.net/v1/phone-numbers \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const response = await fetch("https://developers-api.chatmaid.net/v1/phone-numbers", {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import requests
import os
response = requests.get(
"https://developers-api.chatmaid.net/v1/phone-numbers",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": [
{
"id": "507f1f77bcf86cd799439011",
"phoneNumber": "+15551234567",
"displayName": "Primary Sender",
"connectionStatus": "connected",
"lastConnectedAt": "2026-02-06T14:10:00.000Z",
"lastDisconnectedAt": null,
"createdAt": "2026-02-01T10:20:00.000Z",
"updatedAt": "2026-02-06T14:10:00.000Z"
}
]
}
```
## Get One Number
### GET /v1/phone-numbers/:id
Get a single phone number using either internal ID or URL-encoded E.164 number.
### Read One Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `id` | Path | `string` | Yes | Accepts phone ID or URL-encoded E.164 (example: %2B15551234567). |
| Required scope | - | `phone_numbers:read` | Yes | Also accepts phone-numbers:read. |
| Status codes | - | `200 \| 401 \| 403 \| 404 \| 429` | Yes | Typical read endpoint responses. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/phone-numbers/%2B15551234567" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const phoneRef = encodeURIComponent("+15551234567");
const response = await fetch(`https://developers-api.chatmaid.net/v1/phone-numbers/${phoneRef}`, {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
from urllib.parse import quote
phone_ref = quote("+15551234567", safe="")
response = requests.get(
f"https://developers-api.chatmaid.net/v1/phone-numbers/{phone_ref}",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"phoneNumber": "+15551234567",
"displayName": "Primary Sender",
"connectionStatus": "connected",
"lastConnectedAt": "2026-02-06T14:10:00.000Z",
"lastDisconnectedAt": null,
"createdAt": "2026-02-01T10:20:00.000Z",
"updatedAt": "2026-02-06T14:10:00.000Z"
}
}
```
## Connection Status
### GET /v1/phone-numbers/:id/status
Get up-to-date WhatsApp connection state by internal ID or URL-encoded E.164 number.
### Status Request Contract
| Parameter | In | Type | Required | Rules |
| --- | --- | --- | --- | --- |
| `id` | Path | `string` | Yes | Accepts phone ID or URL-encoded E.164 (example: %2B15551234567). |
| Required scope | - | `phone_numbers:read` | Yes | Also accepts phone-numbers:read. |
| Status codes | - | `200 \| 401 \| 403 \| 404 \| 429` | Yes | Typical status endpoint responses. |
```bash
curl -X GET "https://developers-api.chatmaid.net/v1/phone-numbers/%2B15551234567/status" \
-H "Authorization: Bearer sk_test_xxx"
```
```ts
const phoneRef = encodeURIComponent("+15551234567");
const response = await fetch(`https://developers-api.chatmaid.net/v1/phone-numbers/${phoneRef}/status`, {
headers: { Authorization: "Bearer " + process.env.CHATMAID_API_KEY },
});
const payload = await response.json();
```
```python
import os
import requests
from urllib.parse import quote
phone_ref = quote("+15551234567", safe="")
response = requests.get(
f"https://developers-api.chatmaid.net/v1/phone-numbers/{phone_ref}/status",
headers={"Authorization": f"Bearer {os.environ['CHATMAID_API_KEY']}"},
)
payload = response.json()
```
```json
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"phoneNumber": "+15551234567",
"connectionStatus": "connected",
"lastConnectedAt": "2026-02-06T14:10:00.000Z",
"lastDisconnectedAt": null,
"updatedAt": "2026-02-06T14:10:00.000Z"
}
}
```
Phone registration and connect/disconnect lifecycle remain dashboard-managed. Standard API endpoints are read-focused for integration runtime discovery.
---
# Webhooks
Source: https://developers.chatmaid.net/docs/webhooks
Description: Consume webhook events, verify signatures, and handle retries.
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';
## Webhook Overview
Webhooks are optional. You can poll message status using `GET /v1/messages/:messageId`, or receive push updates on your webhook endpoint.
Webhook destination and subscribed events are configured in the Chatmaid dashboard.
Use a single API domain; environment is inferred from key prefix:
- `https://developers-api.chatmaid.net` + `sk_test_*` for sandbox behavior
- `https://developers-api.chatmaid.net` + `sk_live_*` for production behavior
Integration host: `https://developers-api.chatmaid.net`.
## Events and Signatures
Supported events include:
- `message.sent`
- `message.failed`
- `phone.connected`
- `phone.disconnected`
Each webhook includes these headers:
- `Content-Type: application/json`
- `X-Chatmaid-Event`
- `X-Chatmaid-Signature`
```ts
import crypto from "crypto";
function verifySignature(rawBody: string, signatureHeader: string, secret: string) {
// signature format: t=1700000000,v1=
const pairs = Object.fromEntries(
signatureHeader.split(",").map((part) => {
const idx = part.indexOf("=");
return [part.slice(0, idx), part.slice(idx + 1)];
})
);
const timestamp = pairs.t;
const expected = crypto
.createHmac("sha256", secret)
.update(timestamp + "." + rawBody)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(pairs.v1), Buffer.from(expected));
}
```
```python
import hmac
import hashlib
def verify_signature(raw_body: str, signature_header: str, secret: str) -> bool:
# signature format: t=1700000000,v1=
parts = dict(part.split("=", 1) for part in signature_header.split(","))
payload = (parts["t"] + "." + raw_body).encode("utf-8")
expected = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(parts.get("v1", ""), expected)
```
```bash
# Webhook requests are sent by Chatmaid to your server.
# Use cURL locally only to simulate payload delivery to your endpoint:
curl -X POST https://your-receiver.example/webhooks/chatmaid \
-H "Content-Type: application/json" \
-H "X-Chatmaid-Event: message.sent" \
-H "X-Chatmaid-Signature: t=1700000000,v1=" \
-d '{"event":"message.sent","timestamp":"2026-02-06T14:20:00.000Z","data":{"messageId":"msg_abc123"}}'
```
## Payloads
```json
{
"event": "message.sent",
"timestamp": "2026-02-06T14:20:00.000Z",
"data": {
"messageId": "msg_abc123def456",
"from": "+15551234567",
"to": "+15557654321",
"status": "sent",
"environment": "live",
"sentAt": "2026-02-06T14:19:12.000Z"
}
}
```
## Retries and Debugging
Failed webhook deliveries are retried up to 3 attempts with exponential backoff: 1 minute, 5 minutes, then 15 minutes after the initial attempt. Your endpoint should return HTTP 2xx quickly and process events asynchronously.
Your webhook handler must be idempotent. Retries can deliver the same event more than once.