---
title: "Public API Errors and Error Codes"
description: "Public API errors arrive in the GraphQL errors array. Read extensions.code for the reason, such as Unauthorized or NotEnoughPermissions, and keep traceId."
canonical_url: https://chatfuel.com/docs/public-api/errors
markdown_url: https://chatfuel.com/docs/public-api/errors.md
last_updated: 2026-10-06
lang: en
site: https://chatfuel.com/docs
llms_txt: https://chatfuel.com/llms.txt
---

# Public API Errors and Error Codes

Public API errors arrive in the GraphQL errors array. Read extensions.code for the reason, such as Unauthorized or NotEnoughPermissions, and keep traceId.

The Chatfuel Public API reports problems in the standard GraphQL `errors` array, usually with HTTP status 200. Each error carries a machine-readable `code` in its `extensions`, such as `Unauthorized`, `NotEnoughPermissions` or a business code like `ContactDoesNotExist`, plus a `traceId` that Chatfuel support can use to find the request. Base your error handling on the code, never on the message text.

## What an error response looks like [#what-an-error-response-looks-like]

The gateway in front of the Chatfuel services wraps errors coming from a service: the outer error has a generic message, and the original errors are in `extensions.errors`. A virtual user calling `createVirtualUser` gets a response like this:

```json
{
  "data": null,
  "errors": [
    {
      "message": "Failed to fetch from Subgraph 'useraccounting'.",
      "extensions": {
        "errors": [
          {
            "message": "auth error",
            "path": ["createVirtualUser"],
            "extensions": {
              "code": "NotEnoughPermissions",
              "service": "useraccounting",
              "traceId": "4bf92f3577b34da6a3ce929d0e0e4736"
            }
          }
        ]
      }
    }
  ]
}
```

The inner `extensions` can contain:

* `code`: why the operation failed.
* `service`: the Chatfuel service that answered.
* `traceId`: the request's trace ID. The same value is returned in the `chatfuel-trace-id` response header.
* `data`: for some business errors, a JSON string with details, for example which limit was reached.

Errors raised by the gateway itself, such as a syntax error or an unknown field, come without the wrapper and without a `code`; the `message` explains the problem. When some fields of a query fail and others succeed, `data` contains the successful parts and `errors` describes the rest.

## How to read error codes [#how-to-read-error-codes]

Collect codes from both levels so your code works with wrapped and unwrapped errors:

```js
function errorCodes(errors = []) {
  return errors.flatMap((error) => [
    error.extensions?.code,
    ...(error.extensions?.errors ?? []).map((inner) => inner.extensions?.code),
  ]).filter(Boolean);
}

const body = await res.json();
const codes = errorCodes(body.errors);
if (codes.includes('Unauthorized')) {
  // refresh or replace the token
}
```

## Authentication and permission errors [#authentication-and-permission-errors]

These two codes come with the message `auth error`:

* `Unauthorized`: the request has no valid token. The token is missing, mistyped, revoked or regenerated, the header lacks `Bearer `, or the virtual user was removed.
* `NotEnoughPermissions`: the token is valid, but its owner may not run this operation. Either a virtual user called an operation that needs a [personal API token](https://chatfuel.com/docs/public-api/authentication), or the role is too low, for example an Agent editing a flow. It also appears when the ID belongs to another bot.

## Business error codes [#business-error-codes]

Business errors come with the message `service error` and a code from the `DefinedErrorCode` enum of the public schema. Each reference page lists the codes its operations can return. The codes you will meet most often when working with virtual users:

| Code                        | Returned by                                                              | What to do                                              |
| --------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------- |
| `VirtualUserRoleNotAllowed` | `createVirtualUser`, `changeBotMemberRoleV2`                             | Use the `Editor` or `Agent` role                        |
| `VirtualUserNameInvalid`    | `createVirtualUser`                                                      | Use a name of 1 to 100 characters                       |
| `VirtualUsersLimitReached`  | `createVirtualUser`                                                      | Remove unused virtual users; the limit is 20 per bot    |
| `NotVirtualUser`            | `regenerateVirtualUserToken`                                             | Pass the member ID of a virtual user, not of a teammate |
| `VirtualUserNotAllowed`     | Operations a virtual user can never perform, such as accepting an invite | Run the operation with a personal API token             |

Other codes describe the data: `ContactDoesNotExist`, `ContactNoteTooLong`, `BookingEndTimeBeforeStartTime`, `GoodsItemTitleNotUnique`, `FileTooBig` and so on. Their names say what to fix.

## Internal errors [#internal-errors]

`InternalServerError`, with the message `internal server error`, means the request failed on Chatfuel's side. Retry with exponential backoff. If it keeps failing, contact support through the chat in the bottom-right corner of the dashboard and include the `traceId` and the time of the request.

## Common issues [#common-issues]

### "NotEnoughPermissions" for an operation that works in the dashboard [#notenoughpermissions-for-an-operation-that-works-in-the-dashboard]

The dashboard runs as you, with your role. The API call runs as the virtual user, with its Editor or Agent role, and some operations are never available to virtual users. Check the operation's role in the reference, or run it with the personal API token.

### "Unauthorized" right after rotating a token [#unauthorized-right-after-rotating-a-token]

`regenerateVirtualUserToken` and **Regenerate token** in the dashboard invalidate the old token immediately. Update every service that uses it.

### The response has "errors" but HTTP status 200 [#the-response-has-errors-but-http-status-200]

That is normal for GraphQL. Treat a response as successful only when `errors` is absent.
