Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: CallV2.make
description: 'A method for making Bitrix24 REST API version 2 calls.'
category: 'actions'
audited: 2026-07-02
audited: 2026-08-26
restApiVersion: 'rest-api-ver2'
navigation.title: Call
links:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: CallV3.make
description: 'Method for making Bitrix24 REST API version 3 calls.'
category: 'actions'
audited: 2026-07-02
audited: 2026-08-26
restApiVersion: 'rest-api-ver3'
navigation.title: Call
links:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The `options` object contains the following properties:
| Parameter | Type | Required | Description |
|----|----|----|----|
| **`method`** | `string`{lang="ts-type"} | Yes | REST API method name that returns a data list (e.g., `crm.contact.list`, `tasks.task.list`). |
| **`params`** | `Omit<TypeCallParams, 'pagination' \| 'order'>`{lang="ts-type"} | No | Request parameters, excluding the `pagination` and `order` parameters. The `pagination` parameter is reserved because the method retrieves all data in a single call. The `order` parameter is reserved because cursor-based pagination requires sorting strictly by `cursorIdKey` (which defaults to `idKey`) ascending — see [Limitations](#limitations). Use `filter` and `select` to control the selection. |
| **`params`** | `Omit<TypeCallParamsV3, 'pagination' \| 'order' \| 'filter'> & { filter?: TypeFilterV3 }`{lang="ts-type"} | No | Request parameters, excluding the `pagination` and `order` parameters, and with `filter` narrowed to the `restApi:v3` array form (see [Limitations](#limitations)). The `pagination` parameter is reserved because the method retrieves all data in a single call. The `order` parameter is reserved because cursor-based pagination requires sorting strictly by `cursorIdKey` (which defaults to `idKey`) ascending — see [Limitations](#limitations). Use `filter` and `select` to control the selection. |
| **`idKey`** | `string`{lang="ts-type"} | No | Name of the id field **as it appears in each response item**; its value drives the cursor. Default: `'id'`. Set it to match the id field the method returns. |
| **`cursorIdKey`** | `string`{lang="ts-type"} | No | Field name used in the **request** for `order` and the `[field, '>', n]` page filter. Defaults to `idKey`. Set it only when the sortable / filterable field name differs from the response field name (e.g. an uppercase request field but a lowercase response id): pass `idKey: 'id', cursorIdKey: 'ID'`. |
| **`customKeyForResult`** | `string`{lang="ts-type"} | Yes | Custom key indicating that the REST API response will be selected by this field. For example: `items` for a list of CRM elements. |
Expand Down Expand Up @@ -118,6 +118,7 @@ Some v3 list methods (e.g. `note.*`) also return a `nextCursor`{lang="ts-type"}

- **Page size**: Bitrix24 REST API version 3 limitation — maximum `1000` records per request.
- **Sorting is fixed**: The method always sorts by `cursorIdKey` (which defaults to `idKey`) ascending, because cursor pagination relies on `[cursorIdKey, '>', nextId]` filters to walk the dataset. A user-supplied `order` value would break that invariant, so the type signature excludes `order` and any value passed at runtime is stripped with a `warning` log entry. To narrow the result set, use `filter` instead.
- **`filter` must be the v3 array form**: `[['id', '>', 100]]`, or the output of `FilterV3.build(...)`. The `restApi:v2` object dialect (`{ '>id': 100 }`) is accepted by `TypeCallParamsV3`{lang="ts-type"} for backward compatibility and works with a plain `call`, but not here: cursor pagination appends `[cursorIdKey, '>', nextId]` to the same filter on every page, so an array is the only shape it can extend. Passing an object throws `SdkError`{lang="ts-type"} with code `JSSDK_ACTION_V3_LIST_FILTER_NOT_ARRAY`. It used to be accepted and then failed mid-walk with `filter is not iterable`.
- **Only for list methods**: Intended only for methods that return data arrays.

## Error Handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: FetchListV2.make
description: 'Returns an AsyncGenerator that allows processing data from list methods of Bitrix24 REST API version 2 as it is received without loading the entire array into memory at once. This is especially useful when working with very large volumes of data.'
category: 'actions'
audited: 2026-08-18
audited: 2026-08-26
restApiVersion: 'rest-api-ver2'
navigation:
title: FetchList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: FetchListV3.make
description: 'Returns an AsyncGenerator that allows processing data from list methods of Bitrix24 REST API version 3 as it is received without loading the entire array into memory at once. This is especially useful when working with very large volumes of data.'
category: 'actions'
audited: 2026-08-18
audited: 2026-08-26
restApiVersion: 'rest-api-ver3'
navigation:
title: FetchList
Expand Down Expand Up @@ -70,7 +70,7 @@ The `options` object contains the following properties:
| Parameter | Type | Required | Description |
|----|----|----|----|
| **`method`** | `string`{lang="ts-type"} | Yes | REST API method name that returns a data list (e.g., `crm.contact.list`, `tasks.task.list`). |
| **`params`** | `Omit<TypeCallParams, 'pagination' \| 'order'>`{lang="ts-type"} | No | Request parameters, excluding the `pagination` and `order` parameters. The `pagination` parameter is reserved because the method retrieves all data in a single call. The `order` parameter is reserved because cursor-based pagination requires sorting strictly by `cursorIdKey` (which defaults to `idKey`) ascending — see [Limitations](#limitations). Use `filter` and `select` to control the selection. |
| **`params`** | `Omit<TypeCallParamsV3, 'pagination' \| 'order' \| 'filter'> & { filter?: TypeFilterV3 }`{lang="ts-type"} | No | Request parameters, excluding the `pagination` and `order` parameters, and with `filter` narrowed to the `restApi:v3` array form (see [Limitations](#limitations)). The `pagination` parameter is reserved because the method retrieves all data in a single call. The `order` parameter is reserved because cursor-based pagination requires sorting strictly by `cursorIdKey` (which defaults to `idKey`) ascending — see [Limitations](#limitations). Use `filter` and `select` to control the selection. |
| **`idKey`** | `string`{lang="ts-type"} | No | Name of the id field **as it appears in each response item**; its value drives the cursor. Default: `'id'`. Set it to match the id field the method returns. |
| **`cursorIdKey`** | `string`{lang="ts-type"} | No | Field name used in the **request** for `order` and the `[field, '>', n]` page filter. Defaults to `idKey`. Set it only when the sortable / filterable field name differs from the response field name (e.g. an uppercase request field but a lowercase response id): pass `idKey: 'id', cursorIdKey: 'ID'`. |
| **`customKeyForResult`** | `string`{lang="ts-type"} | Yes | Custom key indicating that the REST API response will be selected by this field. For example: `items` for a list of CRM elements. |
Expand Down Expand Up @@ -127,6 +127,7 @@ Some v3 list methods (e.g. `note.*`) also return a `nextCursor`{lang="ts-type"}

- **Page size**: Bitrix24 REST API version 3 limitation — maximum `1000` records per request.
- **Sorting is fixed**: The method always sorts by `cursorIdKey` (which defaults to `idKey`) ascending, because cursor pagination relies on `[cursorIdKey, '>', nextId]` filters to walk the dataset. A user-supplied `order` value would break that invariant, so the type signature excludes `order` and any value passed at runtime is stripped with a `warning` log entry. To narrow the result set, use `filter` instead.
- **`filter` must be the v3 array form**: `[['id', '>', 100]]`, or the output of `FilterV3.build(...)`. The `restApi:v2` object dialect (`{ '>id': 100 }`) is accepted by `TypeCallParamsV3`{lang="ts-type"} for backward compatibility and works with a plain `call`, but not here: cursor pagination appends `[cursorIdKey, '>', nextId]` to the same filter on every page, so an array is the only shape it can extend. Passing an object throws `SdkError`{lang="ts-type"} with code `JSSDK_ACTION_V3_LIST_FILTER_NOT_ARRAY`. It used to be accepted and then failed mid-walk with `filter is not iterable`.
- **Only for list methods**: Intended only for methods that return data arrays.

## Error Handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: BatchV2.make
description: 'Method for executing batch requests to Bitrix24 REST API version 2. Allows executing up to 50 commands in a single API call.'
category: 'actions'
audited: 2026-07-02
audited: 2026-08-26
restApiVersion: 'rest-api-ver2'
navigation.title: Batch
links:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: BatchV3.make
description: 'Method for executing batch requests to Bitrix24 REST API version 3. Allows executing up to 50 commands in a single API call.'
category: 'actions'
audited: 2026-07-02
audited: 2026-08-26
restApiVersion: 'rest-api-ver3'
navigation.title: Batch
links:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: BatchByChunkV2.make
description: 'Method for executing batch requests with automatic chunking for any number of commands. Automatically splits large command sets into batches of 50 and executes them sequentially. Use only arrays of tuples or arrays of objects.'
category: 'actions'
audited: 2026-07-02
audited: 2026-08-26
restApiVersion: 'rest-api-ver2'
navigation:
title: BatchByChunk
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: BatchByChunkV3.make
description: 'Method for executing batch requests to Bitrix24 REST API version 3 with automatic chunking for any number of commands. Automatically splits large command sets into batches of 50 and executes them sequentially. Use only arrays of tuples or arrays of objects.'
category: 'actions'
audited: 2026-07-02
audited: 2026-08-26
restApiVersion: 'rest-api-ver3'
navigation:
title: BatchByChunk
Expand Down
7 changes: 6 additions & 1 deletion docs/content/docs/2.working-with-the-rest-api/6.errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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.'
navigation:
title: Errors
audited: 2026-07-03
audited: 2026-08-26
links:
- label: SdkError
iconName: GitHubIcon
Expand All @@ -24,6 +24,10 @@ links:
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`. Since #189 `originalError` is **non-enumerable**: it stays readable as `err.originalError` for local debugging, but a spread `{ ...err }`, `Object.keys(err)`, `JSON.stringify(err)`, or a Sentry-style capture skips it — so the raw transport error (which may carry a webhook secret in its `config`) can't leak through generic serialization. Prefer `code` / `status` / `message` for anything you log.
::caution
**`SdkError`'s `description` is not redacted.** `AjaxError` runs its `requestInfo` through `redactSensitiveParams`; `SdkError` has no equivalent step, because its description is expected to be written by the SDK rather than assembled from input. If you construct an `SdkError` yourself, do not interpolate request params, a filter, a URL or a token into it — a filter alone legitimately carries user data, such as the email or phone number being searched for, and error messages travel into logs and failure reports.
::

- `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).
Expand Down Expand Up @@ -77,6 +81,7 @@ Codes are stable strings — match on them, don't parse messages.
| `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_ACTION_V3_LIST_FILTER_NOT_ARRAY` | 500 | `actions.v3.callList` / `fetchList` | `filter` was the `restApi:v2` object dialect (`{ '>id': 100 }`). These actions emulate keyset pagination by appending `[cursorIdKey, '>', cursor]` to the filter, so only the v3 array form can be extended. Use `[['id', '>', 100]]` or `FilterV3.build(...)`. `callTail` / `fetchTail` are unaffected — they paginate through `cursor` and forward `filter` untouched. |
| `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. |
Expand Down
4 changes: 4 additions & 0 deletions docs/content/docs/2.working-with-the-rest-api/70.core-http.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ Low-level batch call. Three input shapes are accepted:

For most use cases call [`actions.v2.batch.make`](/docs/working-with-the-rest-api/batch-rest-api-ver2/) / [`actions.v3.batch.make`](/docs/working-with-the-rest-api/batch-rest-api-ver3/), which handle the response unwrapping and `returnAjaxResult`.

::note
`BatchRequestEnvelopeV2`{lang="ts-type"} is exported alongside these types but is not something you construct. It names what the `restApi:v2` transport puts on the wire for a batch — `{ halt, cmd }` — which reaches `call` through a parameter typed `TypeCallParams`{lang="ts-type"} and type-checks only because of that type's permissive index signature. It is documented here so an exported symbol is not a mystery, not because a caller needs it. `restApi:v3` has no envelope: the commands are the request body.
::

## Limiter Configuration

```ts-type
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/99.examples/3.webhook-cli-node.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: 'Recipe: Webhook CLI smoke test'
description: 'A 30-line Node script that authenticates against a Bitrix24 portal via inbound webhook and prints the calling user — useful as the first thing you run after creating a webhook.'
audited: 2026-08-24
audited: 2026-08-26
category: 'examples'
featured: true
cookbookOrder: 1
Expand Down
46 changes: 45 additions & 1 deletion packages/jssdk/src/core/actions/v3/_keyset-paginate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
import type { TypeB24 } from '../../../types/b24'
import type { LoggerInterface } from '../../../types/logger'
import type { TypeCallParams } from '../../../types/http'
import type { TypeCallParams, TypeFilterV3 } from '../../../types/http'
import type { AjaxResult } from '../../http/ajax-result'
import { SdkError } from '../../sdk-error'

/**
* Reject a non-array `filter` for the emulated-keyset list actions.
*
* `TypeCallParamsV3.filter` accepts the v3 array of triples AND the v2 object
* dialect, kept for backward compatibility, and that union is right for a plain
* `call`, which forwards the filter untouched. It is wrong for `callList` /
* `fetchList`: those append `[cursorIdKey, '>', cursor]` to the same filter on
* every page, so an array is not a preference, it is the only shape the
* mechanism can extend. Passing the object form used to throw `filter is not
* iterable` from a spread, one page into the walk.
*
* Both action option types already narrow `filter` to {@link TypeFilterV3}, so
* for a TypeScript caller the `asserts` signature adds nothing the parameter
* type has not already done. It exists for the callers the types cannot reach:
* JavaScript, and anyone who wrote `params as any`.
*
* `callTail` / `fetchTail` do NOT need this. They paginate through the separate
* `cursor` parameter and forward `filter` untouched, so the object dialect is
* harmless there — which is why the fix stops at two of the four v3 walkers.
*
* @throws {SdkError} `JSSDK_ACTION_V3_LIST_FILTER_NOT_ARRAY`
*/
export function assertArrayFilter(
filter: unknown,
action: string
): asserts filter is undefined | TypeFilterV3 {
if (filter === undefined || Array.isArray(filter)) {
return
}

// Static text plus the caller-supplied `action` label only. Never interpolate
// the filter, or any other caller value, into an SdkError description: unlike
// AjaxError, SdkError does NOT run its message through
// `redactSensitiveParams`, and a filter legitimately carries user data — the
// email or phone number being searched for.
throw new SdkError({
code: 'JSSDK_ACTION_V3_LIST_FILTER_NOT_ARRAY',
description: `${action}: \`filter\` must be the restApi:v3 array form, e.g. [['id', '>', 100]] or FilterV3.build(...). `
+ `The restApi:v2 object dialect ({ '>id': 100 }) cannot be used here, because keyset pagination extends the filter with a cursor condition.`,
status: 500
})
}

/**
* Thrown by {@link keysetPaginate} when the underlying v3 `call` reports a soft
Expand Down
19 changes: 15 additions & 4 deletions packages/jssdk/src/core/actions/v3/call-list.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import type { ActionOptions } from '../abstract-action'
import type { TypeCallParams, TypeCallParamsV3 } from '../../../types/http'
import type { TypeCallParams, TypeCallParamsV3, TypeFilterV3 } from '../../../types/http'
import { AbstractAction } from '../abstract-action'
import { Result } from '../../result'
import { keysetPaginate, KeysetPaginationError } from './_keyset-paginate'
import { assertArrayFilter, keysetPaginate, KeysetPaginationError } from './_keyset-paginate'

export type ActionCallListV3 = ActionOptions & {
method: string
params?: Omit<TypeCallParamsV3, 'pagination' | 'order'>
/**
* `filter` is narrowed to the v3 array form here, unlike {@link TypeCallParamsV3},
* which also accepts the v2 object dialect for backward compatibility.
*
* Keyset pagination is emulated by appending `[cursorIdKey, '>', cursor]` to
* this filter on every page, so an array is not a preference — it is the only
* shape the mechanism can extend. The object form used to be accepted here and
* then threw `filter is not iterable` at runtime, one page into the walk.
*/
params?: Omit<TypeCallParamsV3, 'pagination' | 'order' | 'filter'> & { filter?: TypeFilterV3 }
idKey?: string
cursorIdKey?: string
customKeyForResult: string
Expand Down Expand Up @@ -88,11 +97,13 @@ export class CallListV3 extends AbstractAction {
this._logger.warning('callList.make: user-provided `order` parameter is ignored because cursor-based pagination requires ordering by cursorIdKey. Use `filter` to narrow results instead.').catch(() => {})
}

assertArrayFilter(params['filter'], 'callList.make')

const { order: _ignoredOrder, ...restParams } = params as TypeCallParams
const requestParams: TypeCallParams = {
...restParams,
order: { [cursorIdKey]: 'ASC' },
filter: [...(params['filter'] || [])],
filter: [...(params['filter'] ?? [])],
pagination: { page: 0, limit: batchSize }
}

Expand Down
Loading
Loading