// Start here

Authentication

API keys, scopes, errors, rate limits and pagination.

API keys#

Send your key as a bearer token on every request:

Header
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.
GET/api/v1/me

The 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.

ScopeAllowsEndpoints
messages:sendSend WhatsApp messagesPOST /messages, POST /otp/send
messages:readRead messages and their delivery statusGET /conversations/{id}/messages
contacts:readList and read contactsGET /contacts, GET /contacts/{id}
contacts:writeCreate and update contactsPOST /contacts, PATCH /contacts/{id}
conversations:readList and read conversationsGET /conversations, GET /conversations/{id}
broadcasts:sendLaunch broadcast campaignsPOST /broadcasts, GET /broadcasts/{id}
webhooks:manageRegister and manage outbound event webhooksPOST /webhooks, GET /webhooks, GET /webhooks/{id}, PATCH /webhooks/{id}, DELETE /webhooks/{id}
otp:verifyVerify one-time passwordsPOST /otp/verify

Scopes cannot be edited

To change what a key can do, create a new key with the right scopes, switch your integration over, then revoke the old one.

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.

StatuscodeMeaning
401unauthorizedMissing, malformed, unknown, revoked or expired key
403forbiddenValid key without the scope this endpoint needs
400bad_requestThe request body or query is malformed
404not_foundNo such resource in this key's project
429rate_limitedToo many requests — see below
500internalOur 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:

HeaderMeaning
Retry-AfterSeconds until you may retry
X-RateLimit-LimitRequests allowed per window
X-RateLimit-RemainingRequests left in this window
X-RateLimit-ResetUnix 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;
}