
# Error codes

Vibecode API error code reference: the unified response format, the code list, common causes, and how to fix them. Applies to all `/v1/...` endpoints.

**Base URL:** `https://vibecode.bitrix24.com/v1` | **Authorization:** `X-Api-Key`

## Documentation sections

- [Authorization, keys and permissions](/docs/errors/auth) — `MISSING_API_KEY`, `INVALID_API_KEY`, `TOKEN_MISSING`, `SCOPE_DENIED`, `BITRIX_ACCESS_DENIED`, `WRITE_BLOCKED_READONLY_KEY`
- [Request and data](/docs/errors/request) — `VALIDATION_ERROR`, `INVALID_PARAMS`, `MISSING_REQUIRED_FILTER`, `BATCH_LIMIT_EXCEEDED`, `ENTITY_NOT_FOUND`
- [Billing and plans](/docs/errors/billing) — `BILLING_EXHAUSTED`, `COMMERCIAL_PLAN_REQUIRED`, `INT_TARIFF_REQUIRED`
- [Limits, queues, and pauses](/docs/errors/limits) — `RATE_LIMITED`, `CALLER_RATE_LIMITED`, `ERROR_LOOP_DETECTED`, `QUEUE_OVERFLOW`, `QUEUE_TIMEOUT`, `TIMEOUT_QUARANTINE`, `OPERATION_TIME_LIMIT`
- [Bitrix24 and the platform](/docs/errors/platform) — `BITRIX_ERROR`, `METHOD_NOT_YET_AVAILABLE`, `BITRIX_UNAVAILABLE`, `BITRIX_TIMEOUT`, `INTERNAL_ERROR`
- [What is safe to retry](/docs/errors/retry-safety) — whether the target call reached Bitrix24 and what to do with a read or write for each temporary code
- [Retries and error handling in code](/docs/errors/handling) — when to retry a request, and when to re-read the entity first

## Quick start

Request a deal that does not exist in your Bitrix24 account. The response comes with HTTP status `404` and the refusal code in the `error.code` field:

```bash
curl "https://vibecode.bitrix24.com/v1/deals/999999999" \
  -H "X-Api-Key: YOUR_API_KEY"
```

```json
{
  "success": false,
  "error": {
    "code": "ENTITY_NOT_FOUND",
    "message": "Item not found"
  }
}
```

Every `/v1/...` route returns the same envelope. Its fields are described in "Error response format" below.

## Full example

The client makes three requests: it reads the list of deals, requests a deal that does not exist, and calls a path with a typo. Each refusal is handled by `error.code`, not by the text of `error.message`, because the language of the message is not known in advance. On `RATE_LIMITED` the client waits for the number of seconds in the `Retry-After` header and retries the request at most twice.

```javascript
const BASE = 'https://vibecode.bitrix24.com/v1'
const API_KEY = 'YOUR_API_KEY'

async function call(path, attempt = 1) {
  const res = await fetch(`${BASE}${path}`, { headers: { 'X-Api-Key': API_KEY } })
  const body = await res.json()
  if (body.success) return body

  const { code, message } = body.error
  switch (code) {
    case 'ENTITY_NOT_FOUND':
      console.log(`${path}: the record does not exist — retrying will not help`)
      return null
    case 'ROUTE_NOT_FOUND':
      console.log(`${path}: no such route — check the path against GET /v1/guide`)
      return null
    case 'SCOPE_DENIED':
      console.log(`${path}: the key lacks a scope — ${body.error.userMessage ?? message}`)
      return null
    case 'RATE_LIMITED': {
      if (attempt >= 3) throw new Error(`${path}: the request limit is still exceeded`)
      const wait = Number(res.headers.get('Retry-After') ?? 1)
      await new Promise((resolve) => setTimeout(resolve, wait * 1000))
      return call(path, attempt + 1)
    }
    default:
      throw new Error(`${res.status} ${code}: ${message}`)
  }
}

const list = await call('/deals?limit=1')
// call() returns null on a handled refusal — it is already printed, nothing to add
if (list) console.log('First deal:', list.data[0]?.id)

await call('/deals/999999999')
await call('/dealz')
```

Output on a test Bitrix24 account:

```
First deal: 575
/deals/999999999: the record does not exist — retrying will not help
/dealz: no such route — check the path against GET /v1/guide
```

Ready-made JavaScript, Python and PHP handlers that decide whether to retry a write after a temporary refusal and how to cap retries for each code are on the [Retries and error handling in code](/docs/errors/handling) page.

## Error response format

Every error response is returned in a unified shape. `error` is an object.

```json
{
  "success": false,
  "error": {
    "code": "ENTITY_NOT_FOUND",
    "message": "Item not found"
  }
}
```

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `success` | boolean | yes | Always `false` on error |
| `error.code` | string | yes | Machine-readable error code. Use it to distinguish error types in client code |
| `error.message` | string | yes | Error description for the developer. The language is not known in advance and depends on the source: the platform's technical messages arrive in English, messages from Bitrix24 arrive in the Bitrix24 account's language, and cloud-provider failure messages are localized to the user's language. Do not parse the text or rely on its language — branch on `error.code` |
| `error.hint` | string \| object | no | Developer hint: what to try next, which limits to watch. It is either a text string or an object. The object form is returned when the error has a predefined recovery procedure. On infrastructure creation `POST /v1/infra/servers` it carries `reason`, `recovery` and `example`, where `example` is a ready-to-send request body that fixes the call. On `413 INLINE_SOURCE_TOO_LARGE` — across three routes at once: code deploy, file upload and server creation — the object carries four strings: `reason` (why the body was refused and what the cap is), `recovery` (which path to send the same archive through), `recoveryAction` (the same action in one line) and `note` (what helps regardless of the path chosen) |
| `error.userMessage` | string | no | Message for the end user, in their interface language. Appears in billing, infrastructure, and plan errors, when the account queue is overloaded, in refusals from the Bitrix24 account's operating-time limiter, and in refusals for a scope missing on the key (`error.cause` = `key_scope_missing`): there it names the missing permission and where it is added, and is shown to the person as is |
| `error.alternatives` | array | no | Ways to resolve the refusal: upgrade the plan, top up the balance, call your own AI model with your own key. Every item carries `type`, and with it either a `url` or a `description`. It arrives in plan and balance refusals |
| `error.details` | object | no | Machine-readable context for specific codes. Examples: `reason` in `INVALID_STATE` and in `ROUTE_NOT_FOUND`, `deployableKeys` in `INFRA_FORBIDDEN_FOR_COWORK_KEY`, `upgradeUrl` in plan and trial refusals, `method` and `switchUrl` in `WRITE_BLOCKED_READONLY_KEY`. The field set depends on the code — read its description |
| `error.warning` | string | no | Appears when the same error repeats on one key — a likely sign of a bug in the client code. The counter is heuristic, so the warning does not guarantee a bug |
| `error.retryAfter` | number | no | Seconds until the next attempt. Appears in the `429`s of the account queue (`QUEUE_OVERFLOW`, `QUEUE_TIMEOUT`), in the `OPERATION_TIME_LIMIT` and `TIMEOUT_QUARANTINE` pauses, and in transient `503`s (`BITRIX_TIMEOUT`, `POOL_EXHAUSTED`, `DB_TRANSIENT`, `SERVICE_UNAVAILABLE`). `ERROR_LOOP_DETECTED` carries the remaining recovery-probe pause in this field and in `Retry-After`, or 60 seconds before another check if time has elapsed but the call is still blocked. A standalone `RATE_LIMITED` has no such body field — the delay arrives in the `Retry-After` header. A platform-built batch suberror with this code carries the delay in `retryAfter` under `data.errors.<id>` or `data[i].error`: there is no separate header for a subcall. In `LARGE_BODY_BACKEND_BUSY` the field is not always present — take the delay from the header there too |
| `error.b24Code` | string | no | The machine-readable reason code from Bitrix24 in `422 BITRIX_ERROR` responses; for the chat routes on the v2 messenger — [load a chat](/docs/chats/messages/load), [counters](/docs/chats/discovery/counters), [messages around a message](/docs/chats/messages/context), the `format=v2` mode, [links and guests](/docs/chats/sharing), [stickers](/docs/chats/stickers), [the task form for a chat](/docs/chats/service/task-form) and [for a message](/docs/chats/service/task-form-message), [the flow form](/docs/chats/service/flow-form) and [Zoom](/docs/chats/service/zoom) — also in `404 ENTITY_NOT_FOUND`. It does not always arrive — only when Bitrix24 sent a separate code |
| `error.release` | string | no | The identifier of the Bitrix24 update the account is waiting for: the module name and the version number in one string, for example `imopenlines 26.700.0`. Appears only in [`METHOD_NOT_YET_AVAILABLE`](/docs/errors/platform#method_not_yet_available-422) |
| `error.scope` | string | no | Refusal scope: `"apiKey"` identifies the calling key, `"portal"` identifies the Bitrix24 account. The specific restrictions and retry rules depend on `error.code`. `"apiKey"` appears in [`ERROR_LOOP_DETECTED`](/docs/errors/limits#error_loop_detected-429), [`OPERATION_TIME_LIMIT`](/docs/errors/limits#operation_time_limit-429), [`TOKEN_REFRESH_FAILED`](/docs/errors/auth#token_refresh_failed-401), [`PORTAL_CREDENTIALS_REJECTED`](/docs/errors/auth#portal_credentials_rejected-401) and [`PORTAL_ADDRESS_CHANGED`](/docs/errors/auth#portal_address_changed-409). `"portal"` appears in [`QUEUE_OVERFLOW`](/docs/errors/limits#queue_overflow-429), [`QUEUE_TIMEOUT`](/docs/errors/limits#queue_timeout-429), [`RATE_LIMITED`](/docs/errors/limits#rate_limited-429) when Bitrix24 refuses the call, [`BITRIX_TIMEOUT`](/docs/errors/platform#bitrix_timeout-503) and [`TIMEOUT_QUARANTINE`](/docs/errors/limits#timeout_quarantine-429). [`FEEDBACK_QUOTA_EXCEEDED`](./feedback/submit.md) uses both values |
| `error.cause` | string | no | Why access was refused: `b24_permission` — the Bitrix24 user the call runs as lacks rights; `b24_scope` — the key's credential lacks a Bitrix24 scope; `key_scope_missing` — the API key itself lacks the scope the route needs; `bot_other_key` — the bot is bound to another API key; `unknown` — the cause cannot be determined. The set is open: read an unknown value as `unknown`. Arrives in `403 BITRIX_ACCESS_DENIED`, in `403 BOT_ACCESS_DENIED` and in `422 BITRIX_ERROR` whose `b24Code` is `ACCESS_DENIED_EXTEND` or `NO_AUTH_FOUND`. `key_scope_missing` arrives in `403 INSUFFICIENT_SCOPE`, `403 INFRA_SCOPE_REQUIRED`, `403 SCOPE_NOT_ALLOWED` of a batch request and in `403 SCOPE_DENIED` of entity routes (including their batch and aggregate calls and `include`), timeline items and Open Channels. Details — [Authorization, keys and permissions](/docs/errors/auth#bitrix_access_denied-403) |
| `error.method` | string | no | The Bitrix24 method that was refused, at most 100 characters. Arrives with `error.cause` when the method is known |
| `error.requiredScope` | string | no | The scope that was missing. With `error.cause` = `b24_scope` — a Bitrix24 scope, and only when the scope of the method is known. With `error.cause` = `key_scope_missing` — the scope the route or the batch call needs; absent from a batch response whose calls need different scopes |
| `error.keyScopes` | array | no | The key's scopes in effect for this request — including the automatically granted `vibe:ai` and `vibe:search`, so the list can be wider than `data.scopes` of `GET /v1/me`. Arrives only with `error.cause` = `key_scope_missing`. Not repeated in the entries of a batch `data.errors` |
| `error.fix` | object | no | What resolves the refusal: `action` — the action, `via` — where it is performed. With `via: "same_key"` it also carries `path` — a ready route with no placeholders — and `body` — a ready request body for it. Arrives with `error.cause`. `action: "none"` means there is no automated action — read `error.hint`. The `action` and `via` sets are open: treat an unknown `action` as `none` and an unknown `via` as requiring human action. Values — [Authorization, keys and permissions](/docs/errors/auth#bitrix_access_denied-403) |

Example response with an extra `hint` field (`429 RATE_LIMITED`) — here the retry delay arrives in the `Retry-After: 2` header only:

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "QUERY_LIMIT_EXCEEDED",
    "hint": "Wait 1-2 seconds and retry. Use POST /v1/batch to combine up to 50 calls in 1 request."
  }
}
```

In a standalone HTTP response, the `Retry-After` header mirrors `error.retryAfter` when that field is present, for compatibility with the HTTP standard. Some refusals carry the delay only in the header, so use the header for a standalone response. A suberror inside a successful batch response has no separate header: if a delay is supplied, take it from that suberror's `retryAfter` field.

## Error codes

The table covers the core codes that appear on any Vibecode API endpoint. Domain codes (`BOT_NOT_FOUND`, `SERVER_NOT_RUNNING`, `AGENT_LIMIT_REACHED`, and similar) are documented on the pages of their respective sections.

### Authorization and keys

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `MISSING_API_KEY` | 401 | Request without the `X-Api-Key` header |
| `INVALID_API_KEY` | 401 | The platform does not recognize the key string: a typo, a value truncated while copying, a key from another environment, or a deleted key. An existing key that has stopped working gets different codes — `KEY_INACTIVE`, `KEY_EXPIRED`, `PORTAL_CREDENTIALS_REJECTED` |
| `KEY_INACTIVE` | 401 | The key was found but is revoked or blocked by the platform. The key string stays the same, and a new key is issued separately — a revoked one cannot be restored |
| `KEY_EXPIRED` | 401 | The key was found, and the expiry time in `expiresAt` has passed. The `status` field stays `ACTIVE` |
| `INVALID_APP_KEY` | 401 | An OAuth endpoint received an `app_key` that was not found or does not belong to an OAuth application |
| `WRONG_AUTH_SCHEME` | 401 | An API key was passed in the `Authorization: Bearer` header. An OAuth application key (`vibe_app_*`) goes in `X-Api-Key`, and `Authorization: Bearer` carries the session token (`vibe_session_*`). For a client that can only send Bearer, use a personal key (`vibe_api_*`) — it is accepted in `Authorization: Bearer` |
| `TOKEN_EXPIRED` | 401 | The OAuth token has expired |
| `TOKEN_REFRESH_FAILED` | 401 | Failed to refresh the OAuth token on the Bitrix24 side |
| `PORTAL_CREDENTIALS_REJECTED` | 401 | Bitrix24 rejected the credentials the key uses to call the Bitrix24 account: the key's webhook was revoked or no longer works. Retrying does not help — the key has to be reconnected |
| `PORTAL_ADDRESS_CHANGED` | 409 | The self-hosted portal moved to a new address, while the key's webhook is still issued for the previous one. The platform does not send the secret where the portal no longer is, so the call is refused before reaching the portal. Retrying does not help — reconnect the key: the platform re-issues the webhook, and the key string and its rights stay the same |
| `WRONG_KEY_TYPE` | 401 | The key type does not match the endpoint: e.g. a management key on an entity route |
| `WRONG_KEY` | 403 | The server exists but is not yours. Operations on the application's content — [deploy](./infra/deploy/deploy.md), [exec](./infra/deploy/exec.md), [upload](./infra/deploy/upload.md) and [log reads](./infra/deploy/logs.md) — are open on any one of three grounds: the server's managing key, a key whose application is bound to that server, or membership in that server's development team. Access tokens and the icon upload require the managing key. Machine management is also open to the Administrator role on the development team. The response carries a `hint` with a two-step recovery: rebind the server in the Vibecode dashboard and switch the key the client sends. Details — [access recovery](./infra/server-access-recovery.md) |
| `TOKEN_MISSING` | 401 | The key has no Bitrix24 credentials. For a personal key (`vibe_api_*`) — no Bitrix24 account webhook: on the entity routes and in `POST /v1/batch` the reason arrives in `error.details`, on the other routes the same code arrives without `details`. For an app key (`vibe_app_*`) — no `Authorization: Bearer` session token was sent |
| `SESSION_REQUIRED` | 401 | A [placement](./apps/placements.md) operation was called without the `Authorization: Bearer` header carrying a session token, on an account that requires it |
| `SESSION_APP_MISMATCH` | 403 | The session in `Authorization: Bearer` was issued to a different app or a different Bitrix24 account than the key in `X-Api-Key`. Send the authorization key of the app that issued the session via [`POST /v1/oauth/token`](./keys-auth/oauth.md) |
| `PERSONAL_KEY_WEBHOOK_SCOPES_INVALID` | 400 | Issuing, updating or rotating a personal key where the only requested scopes are `placement` and `entity`. Such a key sits on a Bitrix24 incoming webhook, and a webhook cannot carry such scopes — add a data scope or register an OAuth application. See [Create an API key](/docs/management-keys/create) |
| `ACCOUNT_PENDING_ERASURE` | 503 | The key owner's account is awaiting data erasure — the key is frozen for that period. The response carries a `Retry-After: 3600` header. It applies to every key of that owner, including a [management key](./management-keys.md). Cancelling the erasure request restores the key — no reissue is needed |

Causes and step-by-step fixes for `MISSING_API_KEY`, `INVALID_API_KEY` and `TOKEN_MISSING` — [Authorization, keys and permissions](/docs/errors/auth).

### Bitrix24 account state

The state of the account a key is bound to is checked on every call. These refusals are not tied to an endpoint: while the account stays in one of the states below, the key receives them on every `/v1/...` route that has no refusal of its own for that state, and retrying changes nothing.

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `PORTAL_SUSPENDED` | 403 | The account is suspended |
| `PORTAL_DELETED` | 403 | The account has been deleted |
| `PORTAL_BLOCKED` | 403 | The account is blocked. The response carries `blockedAt` alongside the code — the moment of blocking in ISO 8601 format. The reason for the block is not disclosed |
| `NO_PORTAL` | 401 | The key is not bound to any account. The [storage](./storage.md) routes are the exception: an unbound key gets `403 STORAGE_REQUIRES_PORTAL_BINDING` there. The same code with HTTP `500` arrives on the [application user authorization](./keys-auth/oauth.md) routes and means something else there — the app is not linked to an account |

### Application user authorization (OAuth)

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `INVALID_REDIRECT_URI` | 400 | `redirect_uri` is not registered for the application at the `GET /v1/oauth/authorize` entry point |
| `INVALID_STATE` | 400 | `state` was not found or had expired (20-minute lifetime) when the Bitrix24 response arrived. `error.details.reason` carries `NOT_FOUND` or `EXPIRED` |
| `INVALID_CODE` | 400 | The authorization code was not found or belongs to another application |
| `CODE_EXPIRED` | 400 | The authorization code has expired — a code is valid for 5 minutes |
| `CODE_ALREADY_USED` | 400 | The authorization code has already been exchanged for a session token |
| `REDIRECT_URI_MISMATCH` | 400 | The `redirect_uri` at code exchange does not match the one passed in the authorization request |
| `DOMAIN_MISMATCH` | 400 | `domain` in `POST /v1/oauth/placement-session` does not match the application's Bitrix24 account |
| `USER_AUTH_REQUIRED` | 401 | The Bitrix24 user token did not confirm authorization on the Bitrix24 account |
| `MISSING_TOKEN` | 400 | `POST /v1/oauth/revoke` without an `Authorization: Bearer` header carrying a session token |

A rejection at the callback step may arrive not as a code but as an `?error=` parameter appended to the `redirect_uri` — `token_exchange_failed`, `invalid_domain` or `profile_fetch_failed`.

Full description of the flow — [Application user authorization](./keys-auth/oauth.md).

### Permissions and scopes

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `SCOPE_DENIED` | 403 | The key lacks the required scope (e.g. `crm` for deals, `imbot` for bots). When the response carries `error.cause` = `key_scope_missing`, the action is in `error.fix` and the text for a person in `error.userMessage` |
| `WRITE_BLOCKED_READONLY_KEY` | 403 | The key is in read-only mode, but the call performs a write |
| `CONNECT_AI_NOT_PERMITTED` | 403 | The key was issued to an application through [Partner Connect](/docs/partner-connect), and the Bitrix24 account administrator has not allowed AI for that application or has revoked the permission. Returned on the AI model routes when the platform has enabled AI approval by account administrators. The key's other permissions keep working and the key does not need to be reissued: once AI is allowed, the same key can access the models again. Procedure — [Managing apps in a Bitrix24 account](/docs/partner-connect#managing-apps-in-a-bitrix24-account) |
| `INFRA_FORBIDDEN_FOR_COWORK_KEY` | 403 | A Cowork key with the `vibe:cowork` scope works with data only and cannot perform write operations — every `/v1/infra/*` method except `GET` requests, every write operation on an application (creating, updating, deleting, relinking, publishing and unpublishing) and writing to source storage. On `POST /v1/portals/:id/activate-market-trial` the same code goes to an agent seat key, while a desktop key is refused earlier and with a different code — `PURPOSE_KEY_FORBIDDEN`, see [Activate the Marketplace trial](/docs/activate-market-trial). The Cowork endpoint `POST /v1/cowork/activate-market-trial` never returns this code at all. What to do next — [Project key for deploy](/docs/cowork/deploy-key). A subscription key issued for a third-party agent never gets a project key (`403 COWORK_HARNESS_KEY_FORBIDDEN`), so for such a key `error.details.requiredAction` names the Cowork desktop app and an ordinary key with the `vibe:infra` scope instead, see [Your own agent on the subscription](/docs/cowork/harness). In `error.details.deployableKeys` the response lists your other active keys that can deploy — `name`, `prefix`, `suffix`, up to five |
| `INFRA_SCOPE_REQUIRED` | 403 | The key lacks the `vibe:infra` scope — infrastructure management is unavailable. Add the scope or use a key with infrastructure permissions. The action is in `error.fix`: while servers are turned off for the Bitrix24 account the scope cannot be added, and `fix.action` is `none`. `fix.via` = `platform` means the platform turned them off. A missing `via` with `action` = `none` only means there is no machine action to take, and names NO cause: other refusals for this scope answer the same way — the account admin turned servers off, the platform could not determine whether servers are on, the key's origin is not established, or the key was issued through [Partner Connect](/docs/partner-connect). Tell the causes apart by the text of `error.userMessage` and `error.hint`, not by `via` |
| `INFRA_DISABLED_FOR_PORTAL` | 403 | The `vibe:infra` scope is explicitly requested when [creating a key](/docs/management-keys/create) or [an application](/docs/apps/create), or added by editing their scopes, and the Bitrix24 account administrator has turned server management off. The gate counts the ADDITION of the scope, not its presence in the body: editing a key or an application that already carries it goes through even on an account where server management is off, and removing the scope is always allowed. Not to be confused with `INFRA_SCOPE_REQUIRED` — that one is about calling infrastructure with a key that lacks the scope, this one about granting the scope in the first place |
| `SERVER_ROLE_FORBIDDEN` | 403 | You are on the server's development team, but the operation goes beyond your role: the Developer role covers work with the code, the Administrator role covers machine management on top of that. `error.hint` carries `yourRole`, `requiredRole`, the denied action `deniedAction` and the list of calls open to you in `allowedHere`. Your access is not broken and needs neither a fresh grant nor an API-key rebind — either continue through the calls in `allowedHere`, or ask the owner to run this operation. Role breakdown — [List servers](./infra/servers/list.md) |
| `KEY_POLICY_READONLY_REQUIRED` | 403 | The portal policy allows regular users to create only read-only keys — issuing a write-enabled key is rejected |
| `RATE_LIMIT_ADMIN_ONLY` | 403 | The request changes the key's own per-minute limit on AI calls — the `rateLimit` field on create or edit — and the caller is not a portal administrator. Through `POST /v1/keys` and `PATCH /v1/keys/:id` a change to this field is refused for administrators too: the value is set in the Vibecode dashboard. Passing the current value unchanged goes through, so a client that sends the whole key object just to rename it never sees this refusal. Leave the field empty and the general platform limit applies — [Request limits](/docs/keys-auth#request-limits) |
| `MANAGEMENT_KEY_READ_ONLY` | 403 | A management key tries to create a feedback ticket — `POST /v1/feedback` or an attachment upload for it. Reading and updating tickets with such a key are allowed — creating them is not. A write blocked by the key's access mode arrives under a different code — `WRITE_BLOCKED_READONLY_KEY` |
| `MANAGEMENT_KEY_NO_ENTITY_ACCESS` | 403 | A management key accesses an entity endpoint — an application key with the required scope is needed |
| `BITRIX_ACCESS_DENIED` | 403 | Bitrix24 refused access. A Bitrix24 user without rights is only one of the causes: a scope missing on the key gives the same refusal. The kind of refusal arrives in `error.cause`, the action in `error.fix` |
| `BOT_ACCESS_DENIED` | 403 | The bot is bound to another API key. `error.cause` is `bot_other_key`. When the calling key can transfer the bot to itself, `error.fix` is `transfer_bot` with a ready `path` and `body`. Details — [Authorization, keys and permissions](/docs/errors/auth#bot_access_denied-403) |
| `OAUTH_REQUIRED` | 403 | The endpoint requires a user context — a `vibe_app_*` key + Bearer token is needed |
| `WAITLIST_PENDING` | 403 | The account is awaiting waitlist activation |
| `OAUTH_SCOPE_CHANGE_REQUIRES_REISSUE` | 403 | An attempt to add a Bitrix24 scope to an OAuth application key (`vibe_app_*`) via `PATCH /v1/keys/:id`, or to an app via `PATCH /v1/apps/:id`. An OAuth application's scopes are fixed at issue time — removals work, additions do not. A new scope comes from re-issuing the authorization key in the Vibecode dashboard: it issues a key with the widened scope set, after which the account completes authorization again. The other path is to create the application with the scopes you need up front |
| `OAUTH_APP_REQUIRED` | 400 | A [placement](./apps/placements.md) operation was called with a personal `vibe_api_*` key. Binding, unbinding, and listing bound placements are available only to the application authorization key `vibe_app_*` |
| `PLACEMENT_SCOPE_MISSING` | 403 | The key lacks the `placement` scope — [binding and unbinding placements](./apps/placements.md) are unavailable |
| `SESSION_REQUIRES_ADMIN` | 403 | [Binding a placement](./apps/placements/bind.md) on a self-hosted account was performed by a user without account-administrator rights |
| `B24_EMBEDDING_APP_NOT_FOUND` | 404 | Bitrix24 does not know the application ID: the local application was deleted or reinstalled on the account. Create the local application again and call `POST /v1/apps/:id/relink-oauth` with the new `bitrixClientId` and `bitrixClientSecret`. Not to be confused with `APP_NOT_FOUND`, which is about the key-to-application link on the Vibecode side |
| `APP_NOT_REGISTERED` | 400 | The application has no Bitrix24 application identifier — a [placement](./apps/placements.md) cannot be bound or unbound |
| `BOX_NO_DEVELOPER_KEY` | 400 | The application author has no developer key configured — placement operations on a self-hosted account are unavailable |
| `OAUTH_APP_KEY_CANNOT_RELINK` | 403 | `POST /v1/apps/:id/relink-oauth` was called with the OAuth application's own key (`vibe_app_*`). Such a key cannot relink the app's credentials — use a personal key (`vibe_api_*`) or the Vibecode dashboard |

Causes and step-by-step fixes for `SCOPE_DENIED`, `BITRIX_ACCESS_DENIED` and `WRITE_BLOCKED_READONLY_KEY` — [Authorization, keys and permissions](/docs/errors/auth).

### Request validation

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `VALIDATION_ERROR` | 400 | The body or query parameters failed schema validation. `message` contains per-field details |
| `INVALID_JSON_BODY` | 400 | The request body cannot be parsed as JSON. It is returned before schema validation, so `message` carries no field details. This code comes from the entity routes `/v1/<entity>`, as well as `/v1/apps`, `/v1/bots`, `/v1/keys`, `/v1/note`, every `/v1/infra/servers/:id` server operation, `/v1/infra/runtimes`, the custom-field routes, the chat routes `/v1/chats/*` and most Open Channels routes `/v1/openlines/*`. Operator actions and opening or starting an Open Channels session respond with `FST_ERR_CTP_INVALID_JSON_BODY` |
| `FST_ERR_CTP_INVALID_JSON_BODY` | 400 | The same case — the body cannot be parsed as JSON — on the other routes: [`POST /v1/search`](./search/run.md), [`POST /v1/research`](./search/research.md), [`POST /v1/batch`](./batch.md) and the rest |
| `CATALOG_NOT_ELIGIBLE` | 400 | A card in the Bitrix24 app catalog cannot be created for this server: it has no subdomain, has no app deployed yet, is an agent runtime or a galaxy host, or has no managing key or portal. See [Publish to the catalog](./infra/servers/b24-catalog-publish.md) |
| `CATALOG_ORPHANED` | 400 | The catalog card was removed on the Bitrix24 side. It can be restored in the Vibecode dashboard, but not through the public API |
| `fst_err_ctp_invalid_json_body` | 400 | The same case on the OpenAI-compatible [AI Router](./ai.md) routes — `/v1/ai/`, `/v1/chat/`, `/v1/models`, `/v1/audio/`. There the codes are lowercased and the response uses the OpenAI envelope: `error.type`, `error.code`, with no `success` field |
| `INVALID_PARAMS` | 400 | Bitrix24 returned `INVALID_PARAMS` or refused the call before the method ran because a required parameter had no value (`Could not find value for parameter`) — the parameter is then named in `error.hint`; the route handler found an invalid parameter value; a write put an object or an array into a field declared scalar. See [Request and data](/docs/errors/request) |
| `INVALID_REQUEST` | 400 | The request structure does not match what the endpoint requires — for example, `calls` in `/v1/batch` is an empty array or contains more than 50 elements, [creating a server](./infra/servers/create.md) without its required fields, a non-object sent as the body on the custom-field routes, or a non-object (`null`, an array, a string, a number, a boolean) sent as the body on `POST /{entity}/search` |
| `MISSING_PARAMS` | 400 | A required parameter explicitly listed in the endpoint schema was not passed |
| `MISSING_REQUIRED_FILTER` | 400 | A required filter was not passed on a list endpoint that needs context: `timelines` (`entityType` + `entityId`), `catalog-products` and `catalog-sections` (`iblockId`), `catalog-product-property-enums` (`propertyId`). The same code answers an [activities aggregation](./entities/activities/aggregate.md) with no narrowing filter — there any one of several narrowing filters is enough, and `message` lists them |
| `MISSING_REQUIRED_PARAMS` | 400 | Required context parameters for searching or listing nested data were not passed: `files` requires `folderId`, `folders` requires `parentId`, `calendar-events` requires `type` and `ownerId`. `message` lists the missing fields |
| `MISSING_REQUIRED_FIELDS` | 400 | A body field declared required for creating the entity was not passed. `message` names the missing field |
| `MISSING_FIELD` | 400 | Creating a [custom field](./userfields.md) without `userTypeId`. `message` gives examples of the allowed types |
| `EMPTY_CREATE_BODY` | 400 | The create request body is empty — no field was passed. The same code covers a body that is not a JSON object at all (`null`, an array, a string, a number, a boolean) — no recognizable field can be found in such a body |
| `EMPTY_UPDATE_BODY` | 400 | The update request body is empty — no field was passed. The same code covers a body that is not a JSON object at all (`null`, an array, a string, a number, a boolean) |
| `NESTED_FIELDS_ENVELOPE` | 400 | An entity write route that wraps fields itself receives an object under `fields`, although the schema does not declare that field. That call is not written; pass the fields flat at the JSON root. In a mixed global batch, other valid calls may run. Global batch uses this code in the call error; import and per-entity batch keep their `IMPORT_ITEM_VALIDATION` / `BATCH_ITEM_VALIDATION` codes and include `NESTED_FIELDS_ENVELOPE` in the message. See [Request and data](./errors/request.md) |
| `INVALID_FILTER_FIELD` | 400 | The filter field name starts with the Bitrix24-native prefix `@` (IN) or `!@` (NOT IN) — use the `$in` / `$nin` operators instead |
| `INVALID_DUPLICATE_FILTER_FIELD` | 400 | Two conditions in one filter resolve to the same Bitrix24 filter name, so the second would silently replace the first: the schema name and the Bitrix24-native name, the same name in different letter case, two spellings of one operator, paired date field names (`updatedAt`/`updatedTime`, `createdAt`/`createdTime`). Two further pairs depend on the entity: the `UF_` form together with the camelCase spelling collapses into one name only on entities with the older naming style and only for the spelling the platform converts: the letters-only one (`ufCrmProjectCode`) everywhere on such entities, and the digit-suffixed one (`ufCrm_1698325419`) on requisites only; on the other entities a digit-suffixed pair is not refused, while `$ne` together with `$nin` produces one `!` prefix on EVERY entity except CRM items (the two boundaries differ). `message` names both conditions and the shared name. Send one of the two. Operators that produce different names remain allowed — see [One field — one spelling](/docs/filtering#one-field-one-spelling) |
| `UNKNOWN_FILTER_FIELD` | 400 | Filtering on a field that is not in the entity schema (for entities with a full field schema). When the Vibecode validator rejects the field, `message` lists the allowed names after the word `Available`. When Bitrix24 rejects it, the field list arrives in `error.hint` |
| `UNKNOWN_SELECT_FIELD` | 400 | A `select` field name the entity does not have. Rejected on the entities whose field set has been verified against Bitrix24 — the list is in the `select` parameter description in the [API overview](./entity-api.md). `message` lists the allowed names after the word `Available`. On every other entity the request runs and the name is reported as a warning in `meta.warnings`. A third outcome covers listing and searching requisites and bank details: those routes pass the name on to Bitrix24, so the account decides — an account that has no such field rejects the whole call with `422 BITRIX_ERROR`, an account that accepts the name answers with a warning. Reading a single record on those same entities picks the fields on the Vibecode side and always answers with a warning. When `*` is present in `select`, an unknown name is never rejected — a warning arrives instead. The static schema does not check custom fields (`UF_*`, `ufCrm*`), the exact extra fields of a particular method, product properties on their own entity (`PROPERTY_295` on products, `property295` on catalog products, catalog SKUs, catalog offers and catalog services), or SPA relations such as `parentIdN`; they are passed to the method without `UNKNOWN_SELECT_FIELD`. For these names, the absence of a warning does not prove that the field exists or that the method understands its spelling |
| `SELECT_FIELD_NOT_RETURNED` | 400 | A `select` field name the entity DOES have but never returns: `GET /v1/{entity}/fields` shows it with `notReturned: true`. It gets its own code because `UNKNOWN_SELECT_FIELD` claims the name does not exist, while the entity's own field catalog publishes it. Drop it from `select` — the remaining fields come back in the response |
| `UNKNOWN_SORT_FIELD` | 400 | Sorting by a field that does not exist (for entities whose sort validator is active) |
| `UNKNOWN_STAGE` | 400 | Creating a deal or a lead with a stage the account's dictionary does not hold: [`POST /v1/deals`](./entities/deals/create.md) checks `stageId` against the stages of the funnel named by `categoryId` (without it — the main one, `DEAL_STAGE`), [`POST /v1/leads`](./entities/leads/create.md) checks `stageId` / `statusId` against the lead statuses (`STATUS`). The comparison is literal, as in Bitrix24: letter case, spaces and the funnel prefix `C{categoryId}:` must match the dictionary, and another funnel's stage without its `categoryId` does not match either. The record is NOT created. `message` names the key, the dictionary and the closest exact spelling or the `categoryId` to pass, `details.knownStages` lists the dictionary's stages (up to 50), `details.entityId` says which dictionary to read: `GET /v1/statuses?filter[entityId]=…`. Any spelling of the key Bitrix24 reads as `STAGE_ID` (`STAGE_ID`, `stage_id`, `StageId`) is checked the same way; when several spellings are sent, the last one in the body counts and the error names it in `details.field`. A list or an object in place of a stage name under a raw spelling of the key (`STAGE_ID`, `stage_id`…) is refused too (Bitrix24 puts such a record on the default stage; under `stageId` / `statusId` the shape check refuses it earlier — `400 INVALID_PARAMS`): that refusal needs no dictionary, so `details.knownStages` is empty there and `details.stageId` names the shape of the value ("an array", "an object"). An empty stage (`""`, `null`) is not checked — the record lands on the default stage. Previously such a call answered `201` while Bitrix24 silently created the record on the default stage. If the dictionary could not be read or came back incomplete, the write proceeds as before |
| `BATCH_LIMIT_EXCEEDED` | 400 | The request contains more than 50 elements in a bulk operation (`chats`, `task-comments`, and similar) |
| `MESSAGE_REQUIRED` | 400 | `POST /v1/chats/{dialogId}/messages` with no text: the `message` field is empty and there is no `attach` block. `PATCH /v1/chats/{dialogId}/messages/{messageId}` with no text — by the Bitrix24 rule (whitespace only, `[BR]`/`[br]` tags or NUL count as empty, and an empty text would delete the message). Often the text was passed under an unknown field name, for example `text`. The response lists the unrecognized fields |
| `INVALID_EVENT` | 400 | The event code in a Bitrix24 event subscription does not match the `^[A-Z][A-Z0-9_]+(?:\.[A-Z][A-Z0-9_]+)*$` format. See [Bitrix24 event subscriptions](./infra/event-subscriptions.md) |
| `INVALID_APP_PATH` | 400 | The `appPath` delivery path does not start with `/` or contains control characters. See [Bitrix24 event subscriptions](./infra/event-subscriptions.md) |
| `PLATFORM_HANDLER_UNRESOLVABLE` | 400 | The handler address points to the technical address of the app server, and the platform handler could not be resolved. The placement was not registered. See [Bind a placement](./apps/placements/bind.md) |

An empty body sent with the `Content-Type: application/json` header is accepted as `{}` on the `/v1/infra/*` routes, and also on **every** custom-field route: `/v1/userfields/:entity`, `/v1/userfields/:entity/types` and `/v1/userfields/:entity/:id`, the same three paths under `/v1/items/:entityTypeId/userfields`, plus the short invoice paths `/v1/userfields/invoices`, `/v1/userfields/invoices/types` and `/v1/userfields/invoices/:id`. Operations that need no body — [`POST /v1/infra/servers/:id/wake`](./infra/lifecycle/wake.md), [`DELETE /v1/infra/servers/:id/access-tokens/:tokenId`](./infra/access-tokens.md) and the like — process the request normally instead of rejecting it while parsing the body. This accommodates clients that attach this header to every request (axios and PowerShell `Invoke-RestMethod`, for example).

The operation itself validates the request next, and the refusal code depends on the route — take it from the table above or from the page of the operation you call. A body that does not parse as JSON is rejected on these routes with `INVALID_JSON_BODY`, so an empty body and a malformed one are distinct cases.

Causes and step-by-step fixes for `VALIDATION_ERROR`, `INVALID_PARAMS`, `MISSING_REQUIRED_FILTER` and `BATCH_LIMIT_EXCEEDED` — [Request and data](/docs/errors/request).

### Request body size

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `PAYLOAD_TOO_LARGE` | 413 | The request body exceeds this route's size limit |
| `INLINE_SOURCE_TOO_LARGE` | 413 | The body carrying an inline archive or file is over 96 MB on three infrastructure routes — [code deploy](./infra/deploy/deploy.md), [file upload](./infra/deploy/upload.md) and [server creation](./infra/servers/create.md) with the `source` field |
| `LARGE_BODY_BACKEND_BUSY` | 429 | The platform is already handling the maximum volume of large bodies. The request was not executed — retry it after `Retry-After` seconds |

The limit depends on the route: 1 MB by default; record and list element creation and updates, bot and chat files, `note.file.add` — 40 MiB; upload to Drive — 70 MB. A file inside the body travels as base64 and grows by about a third, so with a 40 MiB limit the original file is just under 30 MiB. The same code arrives from the edge layer at its own threshold. A body sent under an undeclared content type — `text/plain`, for example — is refused with `415 FST_ERR_CTP_INVALID_MEDIA_TYPE` on the entity, custom-field, chat, note, key and Cowork routes, while an empty body under that same type is accepted there as `{}`. On `/v1/infra/*` and `/v1/apps`, including source publishing and placements, the body limit for an unrecognized type is one byte, so the same request answers `413`.

A body above 1 MiB counts as large — the same threshold as the default limit — and the total volume of large bodies handled at a time is capped. The refusal occurs where the body limit is raised: entity records, list element writes, bot files and chat files, uploads to Drive (`POST /v1/files/upload`), note files (`POST /v1/note/documents/{documentId}/files`), and also `POST /v1/chat/completions` and `POST /v1/audio/transcriptions` together with their aliases under `/v1/ai/`. When that capacity is exhausted, `429 LARGE_BODY_BACKEND_BUSY` arrives with a `Retry-After: 5` header. A request without a `Content-Length` header gets the same answer once its volume crosses that threshold mid-transfer. Transcriptions are the exception: only a declared `Content-Length` counts there, and an upload without one is bounded by the single 25 MB per-file ceiling. On AI routes this refusal uses the OpenAI-compatible envelope: no `success` field, and the `large_body_backend_busy` code in lowercase. Take the retry delay from the header: the `error.retryAfter` field is not always present in the body.

AI routes are the exception (`/v1/ai/*`, `/v1/chat/*`, `/v1/audio/*`, `/v1/models`): they use an OpenAI-compatible error envelope, and on an oversize body `error.code` carries the framework's internal code in lowercase rather than `PAYLOAD_TOO_LARGE`.

A body that carries an archive or a file inside JSON has its own code and its own ceiling: [code deploy](./infra/deploy/deploy.md) with `source.content`, [file upload](./infra/deploy/upload.md) with `content` and [server creation](./infra/servers/create.md) with the `source` field accept up to 96 MB of body and answer `413 INLINE_SOURCE_TOO_LARGE` above that. What is counted is the body itself: the content travels as base64 and runs roughly a third larger than the raw bytes, so the cap corresponds to about 72 MB of the archive. The decision is based on the `Content-Length` header before the body is read, and `error.hint` names a way to send the same archive through another path. A link (`source.url`, `url`) and a saved version (`source.versionId`) do not fall under this cap — they have their own 500 MB cap.

### File uploads

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `STORAGE_FORBIDDEN_CONTENT_TYPE` | 415 | For `PUBLIC` objects the types a browser renders as a page, runs or sniffs are forbidden: HTML and XHTML, SVG, JavaScript, any XML (`*/xml`, `*+xml`), XSL, `multipart/*`, `message/*`, `unknown/unknown`, `application/unknown`, `*/*`, with or without parameters — they carry a cross-site scripting risk. Upload such a file as `PRIVATE` |
| `STORAGE_INVALID_CONTENT_TYPE` | 400 | `contentType` is not a single `type/subtype` media type with optional `;name=value` parameters, for example it is a comma-separated list |

### Bitrix24 event subscription preconditions

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `NOT_OAUTH_APP` | 400 | The server is not bound to an OAuth application with an `application_token` — an event subscription cannot be registered |
| `NO_USER_TOKEN` | 400 | The app has no OAuth token — authorize the app on the Bitrix24 account first |

Full description of the operations — [Bitrix24 event subscriptions](./infra/event-subscriptions.md).

### Application install through the connector module

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `CONNECTOR_APP_INSTALL_FORBIDDEN` | 403 | A Bitrix24 account administrator has barred this employee from installing applications. The right is granted by an account administrator, and retrying does not change the state. Details — [Creation rights](/docs/access-rights) |
| `CONNECTOR_MODULE_NOT_INSTALLED` | 409 | The connector module is not installed on the account. The state is permanent — retrying is pointless until the module is installed |
| `CONNECTOR_APP_INSTALL_FAILED` | 502 | Another install failure on the connector module side. The request can be retried |
| `CONNECTOR_REST_UNAVAILABLE` | 502 | Bitrix24 refused the issuance and there is no managed remedy: by the platform's data the account's plan or trial period is active. The original reason is in `error.details.reason`, and `error.details.retryable: true` says the state is transient. The response carries no upgrade offer: there is nothing to buy. The platform does not guarantee a fresh read of the account plan before answering |
| `CONNECTOR_PLAN_REQUIRED` | 502 | The Bitrix24 account plan does not include Vibecode, and there is nothing to offer — a self-hosted portal, an account already on a paid plan, or an unrecognized region. The human-readable cause arrives in `error.userMessage`. The state is permanent: retrying will not help until the plan changes. Where access is sold, the same refusal arrives as `402` with a plan paywall code |

The codes arrive on [application creation](./apps/create.md) where the application is installed by the connector module: on a self-hosted account, and on a cloud account once that issuance path is enabled for it. On any of these refusals neither the application nor the paired key is created.

### Bitrix24 module unavailable on the account

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `BIZPROC_MODULE_NOT_ENABLED` | 409 | Business Processes are not available on the Bitrix24 account: the module is not included in the plan, or it is switched off in account settings. Returned by the business process [templates](./entities/bizproc-templates.md), [activities](./entities/bizproc-activities.md) and [automation rules](./entities/bizproc-robots.md) endpoints, and by [running workflows](./automation/workflows.md). In a [batch call](./batch.md) the response stays `200`, and the code arrives in the result of the failed call |
| `LANDING_MODULE_NOT_ENABLED` | 409 | The Sites module is not available on the Bitrix24 account. Returned when reading [sites](./entities/sites.md): list, get, search and aggregate |
| `LISTS_MODULE_NOT_ENABLED` | 409 | The [Universal Lists](./lists.md) module is not available on the Bitrix24 account |
| `MAIL_MODULE_NOT_ENABLED` | 409 | The Mail module or the [mailbox](./mail/mailboxes.md) methods are not available on the Bitrix24 account |
| `TIMEMAN_MODULE_NOT_ENABLED` | 409 | The Bitrix24 account has no [time tracking](./workday.md): the module is not included in the plan, or it is switched off in account settings |

This is a state of the Bitrix24 account, not a request error: while the module is unavailable on the account, a retry returns the same refusal. A Bitrix24 account administrator can enable a switched-off module.

### Resource not found

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `ROUTE_NOT_FOUND` | 404 | The route or HTTP verb does not exist: a typo in the path, a nonexistent entity, an unsupported method, and also an operation the entity does not have. Check the path against the list in `GET /v1/guide`. When the path was built from a Bitrix24 method name, the response additionally names the replacement — see "A Bitrix24 method name instead of a V1 path" below |
| `ENTITY_NOT_FOUND` | 404 | A CRM entity record with the given `id` does not exist. The canonical code for `/v1/deals/:id`, `/v1/contacts/:id`, and similar |
| `NOT_FOUND` | 404 | `GET /:id` only: Bitrix24 returned `success`, but `result` is empty (applies to several smart methods) |
| `OPERATION_NOT_FOUND` | 404 | No deploy operation with this ID is available: the ID does not exist, the operation belongs to another key, or the record has already been removed. All three cases answer identically on purpose — see [Deploy outcome](./infra/deploy/operation-status.md) |

Domain `*_NOT_FOUND` codes (`BOT_NOT_FOUND`, `SERVER_NOT_FOUND`, `AGENT_NOT_FOUND`, `APP_NOT_FOUND`, `PORTAL_NOT_FOUND`, `USER_NOT_FOUND`, `FILE_NOT_FOUND`, `SUBSCRIPTION_NOT_FOUND`) are documented on the pages of their respective sections.

`B24_EMBEDDING_APP_NOT_FOUND` (404) is a separate case: the application exists in Vibecode, but Bitrix24 does not know its identifier. The condition and the recovery steps are in the "Permissions and scopes" group above.

**Two kinds of 404.** The same HTTP status `404` covers two different states — tell them apart by `error.code`. `ROUTE_NOT_FOUND` — the route or verb does not exist, retrying is pointless: check the path against `GET /v1/guide`. `ENTITY_NOT_FOUND` and the domain `*_NOT_FOUND` codes — the route exists and the requested object is not found. Both states use the unified V1 envelope.

The route does not exist:

```json
{
  "success": false,
  "error": {
    "code": "ROUTE_NOT_FOUND",
    "message": "Route GET:/v1/dealz not found. Check GET /v1/guide for available endpoints and verbs."
  }
}
```

The route exists, the object does not:

```json
{
  "success": false,
  "error": {
    "code": "ENTITY_NOT_FOUND",
    "message": "Item not found"
  }
}
```

**A Bitrix24 method name instead of a V1 path.** Vibecode API paths do not mirror Bitrix24 method names, so a request such as `GET /v1/crm.deal.list` answers `404 ROUTE_NOT_FOUND` and does not call the Bitrix24 method. For the cases that have been mapped, the response additionally names the right route in `error.details`.

| `error.details` field | Type | Description |
|---|-----|----------|
| `reason` | string | `BITRIX_METHOD_AS_PATH` — the path was built from a Bitrix24 method name |
| `bitrixMethod` | string | The method name recognized in the path |
| `suggestedEndpoint` | object | The V1 route that solves the same task: `method` and `path` |
| `guide` | object | A pointer to the route reference: `method` and `path` (`GET /v1/guide`) |

A replacement is named for four methods: `catalog.product.list` → [GET /v1/catalog-products](./entities/catalog-products.md), `crm.deal.list` → [GET /v1/deals](./entities/deals.md), `crm.user.list` → [GET /v1/users](./entities/users.md), `crm.deal.search` → [POST /v1/deals/search](./entities/deals/search.md). On any other path `ROUTE_NOT_FOUND` arrives without `details`, and the route has to be found through [`GET /v1/guide`](./keys-auth/guide.md).

```json
{
  "success": false,
  "error": {
    "code": "ROUTE_NOT_FOUND",
    "message": "Route GET:/v1/crm.deal.list not found. Bitrix24 method names are not V1 API paths; use GET /v1/deals instead. Check GET /v1/guide for required parameters and other endpoints.",
    "details": {
      "reason": "BITRIX_METHOD_AS_PATH",
      "bitrixMethod": "crm.deal.list",
      "suggestedEndpoint": { "method": "GET", "path": "/v1/deals" },
      "guide": { "method": "GET", "path": "/v1/guide" }
    }
  }
}
```

Resend the request to `suggestedEndpoint` and take the parameter set from `guide`: a V1 route has its own filters and fields rather than the ones inherited from the Bitrix24 method.

Causes and step-by-step fixes for `ENTITY_NOT_FOUND` — [Request and data](/docs/errors/request).

### State conflicts

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `CONFLICT` | 409 | The current resource state is incompatible with the request |
| `ALREADY_EXISTS` | 409 | A record with these key fields already exists |
| `CURRENCY_MISMATCH` | 409 | The payment currency does not match the currency of its order. Checked on create; an update is not checked, as the request body carries no order ID. Bitrix24 stores a payment in the order currency and never applies the one sent; omit the field to inherit it. See [Create payment](./entities/payments/create.md) |
| `B24_USER_DELETED` | 409 | The Bitrix24 employee who owns the key is no longer active on the account — no key can be issued for them. It arrives wherever a key gets an owner: [application creation](./apps/create.md), [project deploy key issuance](./cowork/deploy-key.md), and key issuance or reissue with a management key ([management keys](./management-keys.md)). The state is permanent — only restoring the employee on the account helps |
| `KEY_ROTATION_RECOVERY_REQUIRED` | 409 | The key's replacement is unfinished: its rotation ended with a `500` response or has not finished yet. It arrives on rotating or deleting such a key and on issuing an [access token](./infra/access-tokens/create.md) with it. The steps are in [Rotate an API key](/docs/management-keys/rotate) |
| `APPLICATION_KEY_RECOVERY_REQUIRED` | 409 | A key with an unfinished replacement cannot be attached to a server or an application. It arrives, among other places, on [server creation](./infra/servers/create.md) when the replacement of the key that made the request is unfinished, and on replacing a Cowork application key when a key attached to that application is involved in an unfinished replacement. A new `Idempotency-Key` value does not lift the refusal |
| `AGENT_WRITE_ALREADY_EXECUTED` | 409 | An [AI agent](./agents.md) sent `DELETE` to the same address again within 30 seconds of the first call. The repeat is not executed. `error.firstStatus` holds the success status code of the first delete, or `null` if it is still running. `error.firstExecutedAt` holds the time of the first call. If the first delete failed, the repeat goes through as usual. Creates and updates are not checked this way, and requests made with other keys are not affected |
| `EVENT_BOUND_ELSEWHERE` | 409 | A Bitrix24 event is already bound to another server of the same OAuth application. See [Bitrix24 event subscriptions](./infra/event-subscriptions.md) |
| `CATALOG_ALREADY_PUBLISHED` | 409 | The server already has a card in the Bitrix24 app catalog. See [Publish to the catalog](./infra/servers/b24-catalog-publish.md) |
| `CATALOG_DELETE_PENDING` | 409 | The catalog card is already queued for deletion — publishing is unavailable in this state |
| `OAUTH_CLIENT_ID_IN_USE` | 409 | `POST /v1/apps/:id/relink-oauth`: the given `bitrixClientId` is already linked to another application. One `client_id` — one application |
| `OPERATION_OUTCOME_EXPIRED` | 410 | The deploy operation is yours and definitely ran, but its outcome is no longer stored (kept for 7 days). See [Deploy outcome](./infra/deploy/operation-status.md) |

### Billing and plans

These errors occur on endpoints that create and wake infrastructure (servers, agents, managed bots). The response includes a localized `userMessage` for display in the client interface.

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `BILLING_EXHAUSTED` | 402 | The balance dropped into the red zone and the account is frozen. A top-up is required |
| `ACCOUNT_FROZEN` | 402 | The billing account is frozen due to a negative balance. The refusal arrives on calls paid for from the Vibe credits balance. The full list is in the "What an account freeze blocks" block below |
| `COMMERCIAL_PLAN_REQUIRED` | 402 | The platform could not find the record of the Bitrix24 account the key is bound to, so it cannot check access. Returned when [creating a server](./infra/servers/create.md) |
| `OPEN_MODE_REQUIRES_COMMERCIAL` | 402 | Switching a server to OPEN or enabling `allowOpenMode` requires current full access: a paid commercial Bitrix24 plan (a Vibe+ plan when Vibe+ is required). Past payments and trial access do not clear the refusal; read `userMessage` and `details.upgradeUrl` |
| `INT_TARIFF_REQUIRED` | 402 | The Bitrix24 account is on a free plan — creating servers, deploying, and issuing keys require a commercial plan (a demo Bitrix24 plan grants a limited trial). Blocking key issuance also blocks [creating an application](/docs/apps/create) — the paired key is issued there. On wake, only a [Galaxy app](./infra/galaxy.md) gets this code. The same code answers app install and placement binding with `403` — see the `403` row below |
| `INT_VIBE_PLUS_REQUIRED` | 402 | The Bitrix24 account is not on a Vibe+ plan. Upgrade the account to a Vibe+ plan and retry |
| `SELFHOSTED_NOT_AVAILABLE` | 402 | Self-hosted Bitrix24 is not available on this installation yet — access is being opened gradually. The account's servers and data are kept as they are; nothing is deleted. No plan purchase clears this refusal: the contact address is support (`details.upgradeUrl` carries a `mailto:`) |
| `TRIAL_PORTAL_LIMIT` | 402 | The overall per-account server limit was exceeded during the trial period |
| `APPLICATION_REUSE_CONFLICT` | 409 | While the platform was picking an existing server to reuse, a parallel request changed the server's binding to the application. `retryable: true`; retry the create request |
| `PLAN_NOT_ALLOWED_ON_TRIAL` | 402 | The requested server/agent plan is unavailable during the trial period |
| `GALAXY_NOT_ON_TRIAL` | 402 | A Galaxy host cannot be created on trial access. Obtain full access with a paid commercial Bitrix24 plan (a Vibe+ plan when Vibe+ is required); for trial deployment use a standalone server with a plan listed in `capabilities.servers.create.limits.allowedPlans` from `GET /v1/me`, within the remaining quota. This refusal does not forbid a `GALAXY_APP` container on an existing eligible host |
| `VIBE_DEMO_EXPIRED` | 403 | The Vibe+ trial on the Bitrix24 account has ended and the server is a trial machine: starting, waking, rebooting and repairing it are refused until the account is on a paid Vibe+ plan. The server and its data are kept |
| `SERVER_WAKE_BLOCKED` | 403 | Server wake-up is blocked for a non-billing reason — for example, platform access ended |
| `INT_TARIFF_REQUIRED` | 403 | [Binding a placement](./apps/placements/bind.md), installing an app, and issuing or rotating a personal key all require a commercial Bitrix24 plan — including on a self-hosted portal, where the key is issued by the connector module |
| `PORTAL_TARIFF_UNREADABLE` | 403 | The state of the account plan could not be confirmed right now. The cause may be temporary (the check queue is busy, a recent attempt, the time allowed for the check ran out) or in the key's rights. Repeat the check through `/v1/me?refresh=tariff` and follow `details.remedy`. `details.requiredTariffs` is empty and no purchase is offered. Contacting support is needed only if the repeated check keeps failing |

**What an account freeze blocks.** The `402 ACCOUNT_FROZEN` refusal arrives on calls paid for from the Vibe credits balance: infrastructure `/v1/infra/...`, storage `/v1/storage/...`, [search](./search/run.md) and [deep research](./search/research.md), AI spend beyond the Bitrix24 account plan's monthly quota, the application's source storage, ticket attachments and [updating a ticket](./feedback/update.md), and also [issuing a project deployment key](./cowork/deploy-key.md), [creating an application](./apps/create.md) and [creating an application from Cowork](./cowork/applications-create.md), redeeming a coupon, resetting limits with a promotion right and starting the Marketplace trial.

Calls that the balance does not pay for keep working under the freeze: requests to your own Bitrix24 through the entity routes and the [batch call](./batch.md), AI within the Bitrix24 account plan's monthly quota, the paid period of a Cowork subscription, the [model list](./ai/models/list.md), the self-description [`GET /v1/me`](./keys-auth/me.md), the [reference](./keys-auth/guide.md), the spec `GET /v1/openapi.json`, the support conversation — filing a ticket, listing tickets, reading one ticket and commenting on it — and the revocation of your own Cowork key.

For every family listed above except AI, the refusal precedes request validation, so a call closed by the freeze returns neither `400` nor `404`. On the AI routes the handler itself makes the decision after parsing the parameters, so a request carrying a bad field returns `400` before the freeze check is reached.

**Important:** the narrowed refusal is being rolled out account by account. Until it reaches your Bitrix24 account, the freeze answers `402 ACCOUNT_FROZEN` on almost any V1 call, including entity reads and the batch call. What stays open in that state is the self-description, the reference, the spec, the same four support-conversation calls, and the revocation of your own Cowork key.

Causes and step-by-step fixes for `BILLING_EXHAUSTED`, `COMMERCIAL_PLAN_REQUIRED` and `INT_TARIFF_REQUIRED` — [Billing and plans](/docs/errors/billing).

### Rate limiting

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `RATE_LIMITED` | 429 | Bitrix24 throttled the request rate or an internal limit was exceeded. A standalone refusal carries the retry delay in the `Retry-After` header (seconds), with no body `retryAfter` field. A platform-built batch suberror carries the delay in `retryAfter` inside a `200` response, with no separate header for the subcall |
| `CALLER_RATE_LIMITED` | 429 | The overall per-minute request limit is exhausted. Platform-wide ceilings: 600 per API key, 1200 across all keys of one owner; bot event polling is counted by separate counters — 120 per bot, 600 per key and 1200 per owner. The effective number is not published in a header ([details](/docs/errors/limits)). The `X-RateLimit-Scope` header names the counter (`key`, `user`, `bot`), `Retry-After` gives the seconds until the one-minute window ends. The body has no `retryAfter` field |
| `ERROR_LOOP_DETECTED` | 429 | A Vibecode-side block: the same request repeats with the same error. A signal of a bug in the client code. Recovery probes require both N requests and a growing pause; a successful call clears the block |
| `OPERATION_TIME_LIMIT` | 429 | Bitrix24 paused THIS method for YOUR key for about 5 minutes: the method exhausted its operating-time budget. The response carries `scope: "apiKey"`, `retryAfter` and the `Retry-After` header. Other methods and other keys on the account keep working |
| `TIMEOUT_QUARANTINE` | 429 | A Vibecode-side block: the method failed to respond within the call timeout several times in a row, and the "account + method" pair was paused. The response carries `scope: "portal"`, `retryAfter` and the `Retry-After` header. The pause is shared by EVERY key on the account and is lifted automatically |

Causes and step-by-step fixes for this group — [Limits, queues, and pauses](/docs/errors/limits).

### Backend and third-party services

| Code | HTTP | When it occurs |
|-----|------|-----------------|
| `BITRIX_ERROR` | 422 | Bitrix24 returned a business error that does not fall under narrower categories (`ACCESS_DENIED`, `NOT_FOUND`, `INVALID_PARAMS`) |
| `AGGREGATION_LIMIT_EXCEEDED` | 422 | [Aggregation](./entity-api.md#aggregation-post-the-5000-record-ceiling) refused to answer: the selection is wider than 5000 records, the estimated call cost exceeded the safe budget, a page of records failed to load — or (for activities with no narrowing filter) Bitrix24 did not answer within the time allowed for one call. `message` says which case it is. There is no `Retry-After` header: the refusal is not transient |
| `STAGE_NOT_APPLIED` | 422 | Bitrix24 accepted the write but did not apply the stage or the funnel: the value that was sent is not in the dictionary. The record has already been created or changed — its actual state arrives in the response `data`. The batch endpoints and import reach the same verdict, only inside a `200`: an item of `POST /v1/{entity}/batch` and a row of `POST /v1/{entity}/import` get `success: false`, this code in `error` and keep their `id`; a sub-call of `POST /v1/batch` moves to `data.errors.<id>` with the record in that error's `data`. On single create this code is never returned: there the stage is checked against the dictionary BEFORE the write and the refusal comes as `UNKNOWN_STAGE` (the record cannot be checked after creation — automation runs there, and an automation rule that fires "on creation" would produce a false refusal; import runs no automation rules) |
| `AMOUNT_NOT_APPLIED` | 422 | The request EXPLICITLY asked for manual amount mode (`isManualOpportunity: true`) on a smart-process item — Bitrix24 accepted the write but did not store the flag. The usual cause is a type with product rows disabled (`isLinkWithProductsEnabled: false`): the amount and the flag are stored only on types with product rows enabled. Manual mode is recognized in every spelling the platform accepts: `true`, `"true"`, `"Y"`, `"yes"`, `"1"`. The amount, if one was sent, is listed in the refusal alongside the flag; an amount sent WITHOUT the flag is not checked — from the response it is indistinguishable from a legitimate recalculation from product rows. The record was already created or updated — its actual state comes back in the response `data`, and the fields that were not applied are listed in `error.details.unappliedFields`. Such a call used to answer `201`/`200` with the amount silently gone. The batch endpoints and import reach the same verdict, only inside a `200`: an item of `POST /v1/items/{entityTypeId}/batch` and a row of `POST /v1/items/{entityTypeId}/import` get `success: false`, this code in `error` and keep their `id`; a sub-call of `POST /v1/batch` moves to `data.errors.<id>` with the record in that error's `data` |
| `METHOD_NOT_YET_AVAILABLE` | 422 | The method ships in a Bitrix24 update that has not reached this Bitrix24 account yet. The response contains an `error.release` field with the update identifier, for example `imopenlines 26.700.0`. This is a gradual-rollout signal, not a call error — [details](/docs/errors/platform#method_not_yet_available-422) |
| `REST_REGISTRATION_FAILED` | 400 | Bitrix24 refused to register the application or an inbound webhook and gave no reason. Comes back from [creating an application](/docs/apps/create) and from issuing a personal key. The response carries `error.details.incidentCode` — a six-character support reference that locates the log entry. Quote it when you contact support |
| `BITRIX_UNAVAILABLE` | 502 | Bitrix24 returned 5xx, did not respond in time, or the connection to Bitrix24 dropped before a complete response |
| `BIND_FAILED` | 502 | Bitrix24 rejected the event registration (`event.bind`) — for example, the account is not on a commercial plan. See [Bitrix24 event subscriptions](./infra/event-subscriptions.md) |
| `WINDOWED_SEARCH_FAILED` | — | No longer returned: when auto-windowing fails completely, `/search` returns the real Bitrix24 code — `UNKNOWN_FILTER_FIELD` / `INVALID_PARAMS` / `BITRIX_ACCESS_DENIED` / `RATE_LIMITED` / `BITRIX_UNAVAILABLE` / `BITRIX_TIMEOUT` (503) / `BITRIX_ERROR` (422), same as for a narrow range |
| `QUEUE_OVERFLOW` | 429 | The account queue is overflowing: too many concurrent Bitrix24 calls. Rejected instantly, `Retry-After` in the header |
| `QUEUE_TIMEOUT` | 429 | The account queue is saturated: more than 80 seconds of waiting on the Vibecode side. The request was NOT sent to Bitrix24 — safe to retry |
| `BITRIX_TIMEOUT` | 503 | Bitrix24 accepted the request but did not respond within 60 seconds — the outcome is unknown. For writes: re-read the entity first, the change may have applied |
| `POOL_EXHAUSTED` | 503 | The service is temporarily overloaded — the database connection pool is exhausted. The response carries `retryAfter` and a `Retry-After` header. A write's outcome is unknown: re-read the state before retrying |
| `DB_TRANSIENT` | 503 | A database transaction closed or expired. The response carries `retryAfter` and a `Retry-After` header. Rolling back a local transaction does not roll back a Bitrix24 call that already completed, so re-read the state before retrying a write |
| `SERVICE_UNAVAILABLE` | 503 | The platform edge could not pass the request to the backend (during a redeploy, for instance) — or did not get an answer in time. The response carries `retryAfter` and a `Retry-After` header. If the request may already have run (the response wait timed out), re-read the entity before retrying a non-idempotent operation |
| `INTERNAL_ERROR` | 500 | An unexpected Vibecode backend error |
| `PLACEMENT_UNBIND_FAILED` | 502 | Bitrix24 did not confirm the removal of placements. [Publishing](./apps/publish.md) and [updating an application](./apps/update.md) are not applied, and the codes arrive in `error.placements`. Returned only on Bitrix24 accounts where the removal check is enabled |
| `NETWORK_DEVKEY_REQUIRED` | 503 | The developer key for the application author has not been issued yet — [binding a placement](./apps/placements/bind.md) is temporarily unavailable. On Bitrix24 accounts with the removal check enabled, [publishing](./apps/publish.md) and [updating an application](./apps/update.md) answer with the same code |

Causes and step-by-step fixes for `BITRIX_ERROR`, `METHOD_NOT_YET_AVAILABLE`, `BITRIX_UNAVAILABLE`, `BITRIX_TIMEOUT` and `INTERNAL_ERROR` — [Bitrix24 and the platform](/docs/errors/platform). For `QUEUE_OVERFLOW` and `QUEUE_TIMEOUT` — [Limits, queues, and pauses](/docs/errors/limits). Target-call delivery and retry safety for reads and writes are collected separately on [What is safe to retry](/docs/errors/retry-safety).

## See also

- [Quickstart](./quickstart.md)
- [Keys and authorization](./keys-auth.md)
- [Limits and optimization](./optimization.md)
- [What is safe to retry](/docs/errors/retry-safety)
- [Batch](./batch.md)
- [CLI and cURL](./cli.md)
- [MCP for AI](./mcp.md)
