Authentication
Bearer tokens

Every request carries a token in the Authorization header. Create one under
Settings → API Access in the dashboard. The full value is shown once — copy it then.
Authorization: Bearer wa_pk_xxxxxxxxxxxxxxxxxxxxxxxx
A token is scoped to one organization. There is no account-level token, and no way to reach a second organization with the same credential.
Keeping tokens safe
A token reads and writes your customers' conversations. Treat it exactly as you would a password:
- Keep it server-side. Never ship one in frontend JavaScript, a mobile binary, or a public repository.
- Give each integration its own token, so you can revoke one without breaking the others.
- Set an expiry where you can, and rotate on a schedule.
- Revoke immediately if a token is exposed — deactivation takes effect on the next request.
Permissions
Authenticating is not the same as being allowed. Each route additionally requires a named permission
on the token, and the mapping is centralised in one matrix. A route with no entry in that matrix
fails closed with 500 rather than quietly allowing the request — so a newly
added endpoint cannot accidentally ship as authentication-only.
Grant the narrowest set that does the job. A token that only sends order updates needs
chats.reply and nothing else.
| Permission | Grants |
|---|---|
broadcasts.send | 1 endpoint — POST /broadcasts |
broadcasts.view | 3 endpoints — GET /broadcasts, GET /broadcasts/{id}, GET /broadcasts/csv-template |
chats.assign | 4 endpoints — POST /chats/{id}/escalate, POST /chats/{id}/de-escalate, POST /chats/session, POST /chats/{id}/assign |
chats.reply | 1 endpoint — POST /messages |
chats.view | 4 endpoints — GET /chats, GET /chats/{id}, GET /chats/session, GET /chats/{id}/messages |
contacts.edit | 9 endpoints — POST /contacts, POST /contacts/import, PATCH /contacts/{id}, POST /segments, … |
contacts.view | 9 endpoints — GET /contacts, GET /contacts/export, GET /contacts/import-template, GET /contacts/imports, … |
flows.manage | 1 endpoint — POST /flows/trigger |
flows.view | 1 endpoint — GET /flows |
settings.view | 3 endpoints — GET /channels, GET /channels/{id}, GET /organization |
templates.manage | 3 endpoints — POST /templates, POST /templates/sync, DELETE /templates/{id} |
templates.view | 1 endpoint — GET /templates |
ticket_notes.manage | 1 endpoint — POST /tickets/{id}/notes |
tickets.manage | 2 endpoints — POST /tickets, PUT /tickets/{id} |
tickets.view | 2 endpoints — GET /tickets, GET /tickets/{id} |
users.manage | 1 endpoint — GET /users |
What failures look like
| Status | Cause |
|---|---|
401 | No Authorization header, or the token is not recognised. |
403 | The token is real but inactive, expired, or lacks the required permission. The message names the permission. |
The split is deliberate: 401 means "who are you", 403 means "I know who you are and the answer is no". A token that stops working with 403 was revoked or expired — reissue it rather than debugging the header.