// Start here
Authentication
API keys, scopes, errors, rate limits and pagination.
API keys#
Send your key as a bearer token on every request:
Authorization: Bearer wacrm_live_YOUR_KEY- A key belongs to one project and acts only on that project's number, contacts and conversations. If you run several projects, use one key per project.
- Create keys under Settings → API keys (admins and owners). The full key is shown once; we keep only a hash.
- Revoking a key there takes effect on its next request. Keep keys on your server, never in a browser or a mobile app.
/api/v1/meThe project and scopes behind a key
Scope: none — any valid key
Use it to check a key and see what it can do.
curl https://whatsapp.esendblue.com/api/v1/me -H "Authorization: Bearer wacrm_live_YOUR_KEY"Scopes#
A key can do only what its scopes allow, whoever created it. Grant the minimum: a reporting script needs the :read scopes and nothing that sends.
| Scope | Allows | Endpoints |
|---|---|---|
messages:send | Send WhatsApp messages | POST /messages, POST /otp/send |
messages:read | Read messages and their delivery status | GET /conversations/{id}/messages |
contacts:read | List and read contacts | GET /contacts, GET /contacts/{id} |
contacts:write | Create and update contacts | POST /contacts, PATCH /contacts/{id} |
conversations:read | List and read conversations | GET /conversations, GET /conversations/{id} |
broadcasts:send | Launch broadcast campaigns | POST /broadcasts, GET /broadcasts/{id} |
webhooks:manage | Register and manage outbound event webhooks | POST /webhooks, GET /webhooks, GET /webhooks/{id}, PATCH /webhooks/{id}, DELETE /webhooks/{id} |
otp:verify | Verify one-time passwords | POST /otp/verify |
Scopes cannot be edited
Responses and errors#
Every response has one of two shapes:
{ "data": { … } }Branch on error.code, which is stable. error.message is written for people and may be reworded.
| Status | code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing, malformed, unknown, revoked or expired key |
| 403 | forbidden | Valid key without the scope this endpoint needs |
| 400 | bad_request | The request body or query is malformed |
| 404 | not_found | No such resource in this key's project |
| 429 | rate_limited | Too many requests — see below |
| 500 | internal | Our fault. Safe to retry with backoff |
Endpoints add their own codes on top, such as window_closed on a send. Each page lists them.
Rate limits#
Each key may make 120 requests per minute, counted across all our servers. Past that you get 429 rate_limited with these headers:
| Header | Meaning |
|---|---|
Retry-After | Seconds until you may retry |
X-RateLimit-Limit | Requests allowed per window |
X-RateLimit-Remaining | Requests left in this window |
X-RateLimit-Reset | Unix time the window resets |
Sending many messages? Use one /api/v1/broadcasts call instead of a loop over /api/v1/messages. It counts as one request, and it respects WhatsApp's own sending limits for your number.
Pagination#
List endpoints return up to limit rows (default 50, max 100) and an opaque cursor for the next page. Pass the cursor back exactly as you got it. null means you have reached the end.
async function allContacts() {
const rows = [];
let cursor = null;
do {
const url = new URL("https://whatsapp.esendblue.com/api/v1/contacts");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.WHATSAPP_API_KEY}` },
});
const body = await res.json();
rows.push(...body.data);
cursor = body.meta.next_cursor;
} while (cursor);
return rows;
}