---
title: "Error codes and handling"
description: "Reference for SdkError and AjaxError codes raised by the SDK, plus the Bitrix24 REST error codes that surface through them."
canonical_url: "https://bitrix24.github.io/b24jssdk/docs/working-with-the-rest-api/errors"
last_updated: "2026-06-02"
---
# Error codes and handling

> Reference for SdkError and AjaxError codes raised by the SDK, plus the Bitrix24 REST error codes that surface through them.

## Overview

The SDK raises errors through two related classes:

- `SdkError` — thrown by SDK code itself (validation, configuration, deprecated paths, internal invariants). Always carries a `code`, a `status` (HTTP-like), and an optional `originalError`.
- `AjaxError extends SdkError` — thrown when an HTTP call to Bitrix24 fails. Adds `requestInfo` (`method`, `requestId`, request params) so you can correlate with portal-side logs. Since v1.1.2 (#39), `requestInfo` does **not** include the full request URL and credential-bearing fields inside `params` are redacted — the goal is to keep webhook secrets out of `toJSON()` / `toString()` output.

Method-style results that don't throw — `Call`, `CallList`, `Batch`, `BatchByChunk` — surface failures through `Result`/`AjaxResult`: check `.isSuccess` and read `.getErrorMessages()`. `FetchList`, by contrast, **does** throw on failure (the generator can't complete partially).

```ts
import { SdkError, AjaxError } from '@bitrix24/b24jssdk'

try {
  // …SDK calls
}
catch (error) {
  if (error instanceof AjaxError) {
    // network / portal-side failure; error.code is the Bitrix24 code
    console.error(error.code, error.status, error.requestInfo?.method, error.requestInfo?.requestId)
  }
  else if (error instanceof SdkError) {
    // SDK-side issue; error.code starts with JSSDK_
    console.error(error.code, error.status)
  }
  else {
    throw error
  }
}
```

## SdkError codes raised by the SDK

Codes are stable strings — match on them, don't parse messages.

| Code | Status | Where it's thrown | What it means |
| --- | --- | --- | --- |
| `JSSDK_CORE_B24_NOT_INIT` | 500 | `AbstractB24` getters | You called a method on a `B24` instance whose constructor never finished. For `B24Frame` , await `initializeB24Frame()` ; for `B24Hook` , the URL was malformed. |
| `JSSDK_CORE_B24_HTTP_V2_NOT_INIT` | 500 | `getHttpClient(ApiVersion.v2)` | The v2 HTTP client wasn't built (typically a configuration bug). |
| `JSSDK_CORE_B24_HTTP_V3_NOT_INIT` | 500 | `getHttpClient(ApiVersion.v3)` | Same, for v3. |
| `JSSDK_CORE_B24_API_WRONG` | 500 | `getHttpClient` | An unknown `ApiVersion` was requested. |
| `JSSDK_CORE_DEPRECATED_METHOD` | — | logger only | Not thrown — emitted as a `warning` log when you call deprecated `callMethod` / `callBatch` / `callListMethod` / `fetchListMethod` / `callBatchByChunk` . Migrate to `b24.actions.v2.*` / `v3.*` . |
| `JSSDK_CORE_METHOD_AVAILABLE_IN_API_V3` | — | logger only | Warning emitted by `CallV2.make()` when the method also exists in v3. Migrate when you can. |
| `JSSDK_CORE_METHOD_NOT_SUPPORT_IN_API_V3` | 500 | `CallV3.make()` , `BatchV3.make()` , `AjaxResult.getNext()` | You asked v3 to dispatch a method that's not in the v3 surface. Use v2, or split the batch by API version. |
| `JSSDK_CORE_B24_FETCH_LIST_METHOD_API_V2` | 500 | `FetchListV2.make()` generator | An underlying page request failed; the generator stops. Catch around `forawait` . |
| `JSSDK_CORE_B24_FETCH_LIST_METHOD_API_V3` | 500 | `FetchListV3.make()` generator | Same, for v3. |
| `JSSDK_VERSION_MANAGER_NOT_DETECT_FOR_METHOD` | — | `versionManager` | Internal — the method's API version couldn't be inferred. Usually paired with one of the codes above. |
| `JSSDK_BATCH_TOO_LARGE` | 400 | v2 HTTP client | More than 50 commands handed to a single batch. Use `BatchByChunk` . |
| `JSSDK_BATCH_EMPTY` | 400 | v2 HTTP client | Empty `calls` . |
| `JSSDK_BATCH_SUB_ERROR` | 500 | batch processing | Wrapper for a sub-call failure inside a batch. Inspect the per-call result. |
| `JSSDK_INTERACTION_BATCH_BUILD_STRATEGY_V3_EMPTY_COMMAND` | 400 | v3 batch processing | A single command in a v3 batch was empty/malformed. |
| `JSSDK_INTERACTION_BATCH_BUILD_STRATEGY_V3_EMPTY_COMMANDS` | 400 | v3 batch processing | The whole `commands` array was empty. |
| `JSSDK_INTERACTION_BATCH_STRATEGY_V2_EMPTY_COMMANDS` | 400 | v2 batch processing | Same, for v2. |
| `JSSDK_INTERACTION_BATCH_STRATEGY_V2_EMPTY_COMMAND_RESPONSE` | 500 | v2 batch processing | A command in the response had no body — usually portal-side. |
| `JSSDK_INTERACTION_BATCH_EMPTY_PROCESSING_STRATEGY` | 500 | batch processing | Internal — strategy lookup failed. |
| `JSSDK_INTERACTION_BATCH_ROW_FAIL` | 500 | batch row parser | A single batch row could not be parsed. |
| `JSSDK_INVALID_PARAMS` | 400 | HTTP transport | The shape of `params` was rejected before the request was sent. |
| `JSSDK_PARAMS_TOO_LARGE` | 413 | HTTP transport | Serialized request body exceeded the size limit. Split the call. |
| `JSSDK_CALL_ALL_ATTEMPTS_EXHAUSTED` | 500 | HTTP transport | Retries (default 3, configurable via `RestrictionParams.maxRetries` ) exhausted before the call succeeded. |
| `JSSDK_UNKNOWN_ERROR` | 500 | various | Fallback when no more specific code applies. |
| `JSSDK_INTERNAL_ERROR` | 500 | `SdkError.fromException` | Default code when wrapping an unknown exception. |
| `JSSDK_INTERNAL_AJAX_ERROR` | 500 | `AjaxError.fromException` | Default code when wrapping an unknown HTTP error. |
| `JSSDK_CLIENT_SIDE_WARNING` | — | `B24Hook` constructor | Warning emitted when a `B24Hook` is instantiated in the browser — webhooks expose a portal-wide secret and shouldn't ship to client bundles. |

## REST-side codes that come back as `AjaxError`

Bitrix24 returns these in the `error` field of an HTTP response. The SDK lifts them into `AjaxError.code` verbatim — match on the string.

| Code | HTTP status | Meaning | Typical fix |
| --- | --- | --- | --- |
| `expired_token` | 401 | OAuth access token expired. | The SDK auto-refreshes for `B24Frame` and `B24OAuth` ; for custom transports, refresh and retry once. |
| `invalid_token` | 401 | Token is malformed or revoked. | User must re-auth (OAuth flow); webhook URL is wrong. |
| `AUTHORIZE_ERROR` | 403 | Caller is not allowed to perform this action. | Wrong scope on the webhook/app, or the user lacks the access right (e.g. CRM permission). |
| `WRONG_AUTH_TYPE` | 403 | The token type doesn't match the endpoint (e.g. webhook used where OAuth is required). | Use the correct authentication method for the endpoint. |
| `QUERY_LIMIT_EXCEEDED` | 503 | Per-second / per-method rate limit hit. | Already handled by the built-in `RateLimiter` — increase `restrictionParams.maxConcurrent` only if you understand the cost. |
| `OVERLOAD_LIMIT` | 503 | Portal-wide load shedding. | Back off, retry after a few seconds. The SDK's `AdaptiveDelayer` already does this. |
| `ERROR_METHOD_NOT_FOUND` | 400 | Method name is wrong, or the app doesn't have the scope to see it. | Check spelling; check `app.info` 's `SCOPE` against the method's required scope. |
| `INVALID_REQUEST` | 400 | Bad parameters (missing required field, wrong type). | Inspect `error.message` — Bitrix24's description names the offending field. |

This is not exhaustive — Bitrix24 publishes the full method-specific list at [apidocs.bitrix24.com](https://apidocs.bitrix24.com/). Anything not listed here passes through as-is on `AjaxError.code`.

## Handling patterns

### Distinguish SDK bugs from portal-side failures

`AjaxError` extends `SdkError`, so order the `instanceof` checks specifically-first:

<rest-api-version-only>
<template v-slot:rest-api-ver2="">

// @check-ignore: top-level try/catch with return, not valid at module scope

```ts
import { AjaxError, SdkError } from '@bitrix24/b24jssdk'

try {
  await $b24.actions.v2.call.make({ method: 'crm.deal.get', params: { id: 1 } })
}
catch (error) {
  if (error instanceof AjaxError) {
    // Portal-side: error.code is the Bitrix24 code (e.g. expired_token)
    if (error.code === 'expired_token') return refreshAndRetry()
    if (error.code === 'AUTHORIZE_ERROR') return showPermissionDenied()
    throw error
  }
  if (error instanceof SdkError) {
    // SDK-side: error.code starts with JSSDK_
    throw error // these are programmer errors, surface them
  }
  throw error
}
```

</template>

<template v-slot:rest-api-ver3="">

// @check-ignore: top-level try/catch with return, not valid at module scope

```ts
import { AjaxError, SdkError } from '@bitrix24/b24jssdk'

try {
  await $b24.actions.v3.call.make({ method: 'crm.deal.get', params: { id: 1 } })
}
catch (error) {
  if (error instanceof AjaxError) {
    // Portal-side: error.code is the Bitrix24 code (e.g. expired_token)
    if (error.code === 'expired_token') return refreshAndRetry()
    if (error.code === 'AUTHORIZE_ERROR') return showPermissionDenied()
    throw error
  }
  if (error instanceof SdkError) {
    // SDK-side: error.code starts with JSSDK_
    throw error // these are programmer errors, surface them
  }
  throw error
}
```

</template>
</rest-api-version-only>

### Use `isSuccess` for non-throwing methods

`Call`, `CallList`, `Batch`, `BatchByChunk` return a `Result` / `AjaxResult` instead of throwing:

<rest-api-version-only>
<template v-slot:rest-api-ver2="">

// @check-ignore: top-level return in callList error-handling illustration

```ts
const response = await $b24.actions.v2.callList.make({ /* … */ })
if (!response.isSuccess) {
  console.error(response.getErrorMessages().join('; '))
  return
}
const items = response.getData()
```

</template>

<template v-slot:rest-api-ver3="">

// @check-ignore: top-level return in callList error-handling illustration

```ts
const response = await $b24.actions.v3.callList.make({ /* … */ })
if (!response.isSuccess) {
  console.error(response.getErrorMessages().join('; '))
  return
}
const items = response.getData()
```

</template>
</rest-api-version-only>

For batches with `isHaltOnError: false`, `isSuccess` flips false on **any** sub-call failure but `getData()` still contains the successful entries. Iterate and check per-row `.isSuccess` if you need to know which calls passed.

### Catch around `for await` for `FetchList`

<rest-api-version-only>
<template v-slot:rest-api-ver2="">

// @check-ignore: top-level for-await in fetchList error-handling illustration

```ts
try {
  for await (const chunk of $b24.actions.v2.fetchList.make({ /* … */ })) {
    await persist(chunk)
  }
}
catch (error) {
  if (
    error instanceof SdkError
    && error.code === 'JSSDK_CORE_B24_FETCH_LIST_METHOD_API_V2'
  ) {
    // a page request failed; persisted chunks before this point are still good
    return resumeFromLastSavedId()
  }
  throw error
}
```

</template>

<template v-slot:rest-api-ver3="">

// @check-ignore: top-level for-await in fetchList error-handling illustration

```ts
try {
  for await (const chunk of $b24.actions.v3.fetchList.make({ /* … */ })) {
    await persist(chunk)
  }
}
catch (error) {
  if (
    error instanceof SdkError
    && error.code === 'JSSDK_CORE_B24_FETCH_LIST_METHOD_API_V3'
  ) {
    // a page request failed; persisted chunks before this point are still good
    return resumeFromLastSavedId()
  }
  throw error
}
```

</template>
</rest-api-version-only>

### Decide what to retry

| Error condition | Safe to retry? |
| --- | --- |
| `QUERY_LIMIT_EXCEEDED` , `OVERLOAD_LIMIT` | Yes — the built-in limiter already does. |
| `expired_token` (Frame/OAuth) | Yes — already automatic. For custom transports: refresh, retry once. |
| `invalid_token` , `AUTHORIZE_ERROR` , `WRONG_AUTH_TYPE` | No — needs human intervention (re-auth, fix scopes). |
| `ERROR_METHOD_NOT_FOUND` , `INVALID_REQUEST` | No — request is malformed; retrying gives the same error. |
| Network / 5xx without a specific code | Yes — but only via `JSSDK_CALL_ALL_ATTEMPTS_EXHAUSTED` boundary; bump `restrictionParams.maxRetries` if you need more attempts. |

## See also

- [Choosing the right method](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/choosing-the-right-method.md) — picks the right primitive before you have to read this page.
- [Restrictions System](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/limiters.md) — rate limit, operating-time and adaptive delay configuration.
- [`AjaxResult`](https://github.com/bitrix24/b24jssdk/blob/main/packages/jssdk/src/core/http/ajax-result.ts) — full payload surface (`isSuccess`, `getData`, `getErrorMessages`). The v2-only paging helpers `isMore`, `hasMore`, `getNext`, `fetchNext` and `getTotal` are deprecated and slated for removal in 2.0.0; use [`b24.actions.v{2,3}.callList.make`](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-list-rest-api-ver2.md) or [`fetchList.make`](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/fetch-list-rest-api-ver2.md) instead. `getNext()` continues to throw `JSSDK_CORE_METHOD_NOT_SUPPORT_IN_API_V3` against v3 clients.

## Sitemap

See the full [sitemap](/b24jssdk/sitemap.md) for all pages.
