> ## Documentation Index
> Fetch the complete documentation index at: https://docs.connectadesk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Format of Connecta DESK API error responses, the table of ERR_ codes and how to handle each type of failure in your integration.

Every API error comes back in the same format, with a fixed code for your integration to handle and
a text message for people to read.

```json theme={null}
{
  "error": {
    "code": "ERR_API_FORBIDDEN_SCOPE",
    "message": "O token não tem permissão de POST no módulo \"contacts\"."
  }
}
```

<ResponseField name="error.code" type="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.
</ResponseField>

<ResponseField name="error.message" type="string" required>
  Description in Portuguese, meant to show up in logs and on support screens.
</ResponseField>

<ResponseField name="error.details" type="any">
  Extra information, when there is any, such as the list of invalid fields. Most errors do not
  include this field.
</ResponseField>

<Warning>
  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.
</Warning>

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

<Tip>
  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.
</Tip>

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

<Note>
  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.
</Note>

## Business-rule codes

These codes show up when the request is correct, but the current state of the resource does not
allow the operation.

<AccordionGroup>
  <Accordion title="Messages" icon="send">
    | 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. |
  </Accordion>

  <Accordion title="Channels" icon="smartphone">
    | Code | Status | Meaning |
    | - | - | - |
    | `ERR_API_CHANNEL_NOT_FOUND` | 404 | The channel does not exist in this company. |
  </Accordion>

  <Accordion title="Webchat" icon="messages-square">
    | 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). |
  </Accordion>

  <Accordion title="Tickets" icon="message-square">
    | 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`. |
  </Accordion>

  <Accordion title="Tags" icon="tags">
    | Code | Status | Meaning |
    | - | - | - |
    | `ERR_NO_TAG_FOUND` | 404 | The tag does not exist or was deleted. |
  </Accordion>

  <Accordion title="Wrap-up types" icon="award">
    | Code | Status | Meaning |
    | - | - | - |
    | `ERR_API_QUEUE_NOT_FOUND` | 422 | One of the queues in `queues` does not exist in this company. |
  </Accordion>

  <Accordion title="Campaigns" icon="megaphone">
    | Code | Status | Meaning |
    | - | - | - |
    | `ERR_NO_CAMPAIGN_FOUND` | 404 | The campaign does not exist in this company. |
  </Accordion>

  <Accordion title="CRM" icon="kanban">
    | 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. |
  </Accordion>

  <Accordion title="Reports" icon="chart-column">
    | 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. |
  </Accordion>

  <Accordion title="Scheduled messages" icon="calendar">
    | 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. |
  </Accordion>

  <Accordion title="Users" icon="user-cog">
    | 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`. |
  </Accordion>
</AccordionGroup>

## How to handle each type of error

Each status range calls for a different reaction. A `400` 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.

<CodeGroup>
  ```javascript Node.js theme={null}
  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);
    }
  }
  ```

  ```python Python theme={null}
  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)
  ```
</CodeGroup>

<Note>
  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.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.