{
"error": {
"code": "ERR_API_FORBIDDEN_SCOPE",
"message": "O token não tem permissão de POST no módulo \"contacts\"."
}
}
string
required
Fixed identifier of the error. Use this code to decide what your integration does. The message
text may change over time, so do not rely on it.
string
required
Description in Portuguese, meant to show up in logs and on support screens.
any
Extra information, when there is any, such as the list of invalid fields. Most errors do not
include this field.
Treat any response outside the
2xx range as an error before reading data. When the call
fails, data does not exist, and reading response.data.id throws a TypeError that hides the
real cause.Authentication and permission codes
| Code | Status | What to do |
|---|---|---|
ERR_API_UNAUTHORIZED | 401 | Missing, invalid, revoked or expired token, or an unauthorized IP. The response is the same in all of these cases. Check the token in the panel. |
ERR_API_FORBIDDEN_SCOPE | 403 | The token does not have permission for that method on that module. The message says which one. Adjust the token’s permissions. |
ERR_API_NOT_ENABLED | 403 | The company’s plan does not include the public API. |
ERR_NO_PERMISSION | 403 | The operation requires an access level the token does not have. |
ERR_SELF_SERVICE_DISABLED | 403 | Feature available only on self-service installations, such as user invitations. |
When a
401 suddenly starts showing up, the most common cause is that the token has expired or
your server’s outbound IP has changed. Check the token’s expiry date before investigating the
network.Request codes
| Code | Status | What to do |
|---|---|---|
ERR_API_VALIDATION | 400 or 422 | A required field is missing or some value is not accepted. The message says what the problem is. |
ERR_API_INVALID_JSON | 400 | The body is not valid JSON. Check quotes, commas and the Content-Type: application/json header. |
ERR_API_INVALID_PARAMETER | 400 | A parameter has an invalid format, such as text where a number is expected or a date that does not exist. |
ERR_API_NOT_FOUND | 404 | The record does not exist, or the id sent is not a number. |
ERR_API_ROUTE_NOT_FOUND | 404 | The method or path does not exist. Check the verb and whether the base URL includes /api/v1. |
ERR_API_CONFLICT | 409 | A record with that data already exists. |
ERR_API_INVALID_REFERENCE | 422 | One of the ids sent in the body does not exist. |
ERR_API_PAYLOAD_TOO_LARGE | 413 | The body is larger than allowed. |
ERR_API_UPLOAD | 413 or 422 | The file is larger than allowed or the upload was not accepted. |
ERR_API_RATE_LIMIT | 429 | The company reached its limit of requests per minute. Wait for the time given in Retry-After. |
ERR_API_IDEMPOTENCY_KEY_INVALID | 400 | Idempotency-Key longer than 200 characters. |
ERR_API_IDEMPOTENCY_KEY_REUSED | 409 | The same key has already been used with a different body. |
ERR_API_IDEMPOTENCY_IN_PROGRESS | 409 | Another attempt with that key is still in progress. |
ERR_API_INTERNAL | 500 | A failure on our side. It is worth retrying after a few seconds. If it keeps happening, contact support with the method and path. |
An id from another company returns
404, exactly like an id that does not exist. This is on
purpose: the API does not confirm that a record exists outside your account.Business-rule codes
These codes show up when the request is correct, but the current state of the resource does not allow the operation.Messages
Messages
| Code | Status | Meaning |
|---|---|---|
ERR_API_BODY_REQUIRED | 400 | A text message without the body field. |
ERR_API_TEMPLATE_REQUIRED | 400 | A template message without the template field. It also shows up when scheduling a message on an official WhatsApp channel without a template. |
ERR_API_INTERACTIVE_REQUIRED | 400 | An interactive message without the interactive field. |
ERR_API_MEDIA_REQUIRED | 400 | A media message without a file. |
ERR_API_CHANNEL_NOT_FOUND | 404 | The channel does not exist in this company. |
ERR_API_TICKET_NOT_FOUND | 404 | The ticket sent in ticketId does not exist in this company. |
ERR_API_JOB_NOT_FOUND | 404 | The jobId does not exist or belongs to another company. |
ERR_API_TEMPLATE_MEDIA_NOT_ALLOWED | 400 | The template provided does not accept media in the header. |
ERR_API_CONTACT_NOT_IDENTIFIED | 400 | None of ticketId, contactId or number, or only number on a channel without a phone number. |
ERR_API_CONTACT_NOT_FOUND | 404 | The contactId does not exist in this company. |
ERR_API_CONTACT_UNREACHABLE | 422 | The contact cannot receive on this channel: no previous conversation on it (channels without a phone number; on Webchat, no visitor session on this webchat) or no phone number (phone channels). |
ERR_API_INTERACTIVE_INVALID | 422 | Webchat: interactive is outside the WhatsApp format or limits, or is of a type that does not exist on webchat. The message includes the field and the reason. |
ERR_API_NO_WHATSAPP_CREDITS | 402 | WhatsApp Business channel with billing enabled and the company out of credit. The message is not queued. |
ERR_API_ACTIVE_CONTACTS_LIMIT_REACHED | 402 | The company reached its monthly active contacts limit and the recipient has not exchanged messages with it this month. Contacts that already counted this month keep receiving; the count resets on the 1st. Does not apply to web_whatsapp channels. The message is not queued. |
Channels
Channels
| Code | Status | Meaning |
|---|---|---|
ERR_API_CHANNEL_NOT_FOUND | 404 | The channel does not exist in this company. |
Webchat
Webchat
| Code | Status | Meaning |
|---|---|---|
ERR_WEBCHAT_NOT_FOUND | 404 | The slug is not an active webchat of this company. |
ERR_WEBCHAT_SESSION_NOT_FOUND | 404 | The session does not exist on this webchat. |
ERR_WEBCHAT_SESSION_CLOSED | 410 | The session was closed. Open another one (with externalId, the same one is reopened). |
ERR_WEBCHAT_SESSION_BLOCKED | 403 | The session is blocked for sending too many messages (1 hour). |
ERR_WEBCHAT_VISITOR_DATA_REQUIRED | 422 | Required initial visitor data is missing. The message lists the fields (e.g. name,phone). |
ERR_WEBCHAT_MESSAGE_EMPTY | 422 | Message with no text and no file. |
ERR_WEBCHAT_MESSAGE_TOO_LONG | 422 | Text above 4,096 characters. |
ERR_WEBCHAT_MEDIA_TOO_LARGE | 413 | File above 10 MB. |
ERR_WEBCHAT_MEDIA_INVALID_TYPE | 422 | File type not accepted (the type is read from the contents, not the name). |
ERR_WEBCHAT_UPLOAD_LIMIT | 429 | The webchat’s file volume for the last hour is used up. |
ERR_WEBCHAT_INTERACTIVE_REPLY_INVALID | 422 | interactiveReply with an option that is not in the message, or a message the visitor cannot see. |
ERR_WEBCHAT_LOCATION_INVALID | 422 | location out of range (latitude from -90 to 90, longitude from -180 to 180). |
Tickets
Tickets
| Code | Status | Meaning |
|---|---|---|
ERR_NO_TICKET_FOUND | 404 | The ticket does not exist in this company. |
ERR_API_TRANSFER_TARGET_REQUIRED | 400 | To transfer, provide the destination queue or user. |
ERR_API_TICKET_CLOSED | 409 | automation: true on a closed conversation whose protocol is no longer open. |
ERR_API_TICKET_IN_NPS | 409 | automation on a conversation that is in the satisfaction survey. |
ERR_API_TICKET_IN_SERVICE | 409 | automation: true on a conversation with an assigned agent. Remove the agent first, with POST /tickets/{id}/transfer and userId: null. |
Tags
Tags
| Code | Status | Meaning |
|---|---|---|
ERR_NO_TAG_FOUND | 404 | The tag does not exist or was deleted. |
Wrap-up types
Wrap-up types
| Code | Status | Meaning |
|---|---|---|
ERR_API_QUEUE_NOT_FOUND | 422 | One of the queues in queues does not exist in this company. |
Campaigns
Campaigns
| Code | Status | Meaning |
|---|---|---|
ERR_NO_CAMPAIGN_FOUND | 404 | The campaign does not exist in this company. |
CRM
CRM
| Code | Status | Meaning |
|---|---|---|
ERR_NO_BOARD_FOUND | 404 | The board does not exist in this company. |
ERR_NO_BOARD_LIST_FOUND | 404 | The stage does not exist on the board, or the destination stage belongs to another board. |
ERR_NO_CARD_FOUND | 404 | The card does not exist in this company. |
ERR_NO_CONTACT_FOUND | 404 | The contact does not exist in this company. |
Reports
Reports
| Code | Status | Meaning |
|---|---|---|
ERR_COMPANY_OR_DATE_NOT_DEFINED | 400 | date_start or date_finish is missing. |
ERR_API_VALIDATION | 422 | The date is not in the YYYY-MM-DD or YYYY-MM-DD HH:mm:ss format. |
Scheduled messages
Scheduled messages
| Code | Status | Meaning |
|---|---|---|
ERR_API_SCHEDULE_ALREADY_SENT | 409 | The scheduled message has already been sent and cannot be changed. |
ERR_API_SCHEDULE_CANCELED | 409 | The scheduled message was canceled. |
ERR_API_SCHEDULE_NOT_EDITABLE | 409 | The current state of the scheduled message does not allow editing. |
Users
Users
| Code | Status | Meaning |
|---|---|---|
ERR_API_USER_ID_REQUIRED | 400 | The operation requires the user’s id. |
ERR_API_USER_NO_WRITABLE_FIELDS | 400 | No editable field was sent. Email and password belong to the user’s global account and cannot be edited through the API. |
ERR_API_INVITE_ALREADY_ACCEPTED | 409 | The invitation has already been accepted and the person is already a member of the company. To remove their access, use PUT /users/{id}/active. |
ERR_API_PROFILE_NOT_FOUND | 422 | The profileId is not a profile of this company. See the profiles in GET /profiles. |
How to handle each type of error
Each status range calls for a different reaction. A400 comes back the same if you repeat the
call without changing anything. A 429, on the other hand, should be repeated after the indicated
wait, or the operation is lost.
async function handle(res) {
if (res.ok) return res.status === 204 ? null : (await res.json()).data;
const { error } = await res.json().catch(() => ({ error: {} }));
switch (res.status) {
case 401:
// Do not repeat: the token will not get better on its own. Alert whoever operates it.
throw new CredentialError(error.code, error.message);
case 403:
case 404:
case 409:
case 422:
// Final: repeating gives the same result. Fix the call.
throw new RequestError(error.code, error.message, error.details);
case 429:
throw new TemporaryError(Number(res.headers.get('Retry-After') ?? 60));
default:
// 5xx: server failure. A retry with increasing backoff is worth it.
throw new TemporaryError(5);
}
}
def handle(res):
if res.ok:
return None if res.status_code == 204 else res.json().get("data")
error = res.json().get("error", {}) if res.content else {}
if res.status_code == 401:
raise CredentialError(error.get("code"), error.get("message"))
if res.status_code in (403, 404, 409, 422):
raise RequestError(error.get("code"), error.get("message"), error.get("details"))
if res.status_code == 429:
raise TemporaryError(int(res.headers.get("Retry-After", 60)))
raise TemporaryError(5)
Always record the
error.code in your log, together with the method and path you called. With
that information, support can locate the request, and it all fits on one line.
