---
title: "CallTailV3.make"
description: "Method for retrieving all records of a REST API version 3 tail method through the server's native keyset cursor."
canonical_url: "https://bitrix24.github.io/b24jssdk/docs/working-with-the-rest-api/call-tail-rest-api-ver3"
last_updated: "2026-10-01"
---
# CallTailV3.make

> Method for retrieving all records of a REST API version 3 tail method through the server's native keyset cursor.

## Overview

`CallTailV3.make()`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} walks a `restApi:v3` **tail** method — a method that pages by the server's own keyset cursor — and returns every record as one array.

> [!CAUTION]
> **Check that your method publishes a tail endpoint before you reach for this.** It is a separate method from its list counterpart, and few modules have one — sampling ten plausible candidates on a cloud portal in September 2026, only `main.eventlog.tail` answered; the rest returned `METHODNOTFOUNDEXCEPTION`.
> Ask the portal rather than trusting that sample. `rest.documentation.openapi` returns every v3 method it publishes, keyed by path:
> ```ts
> const doc = await $b24.actions.v2.call.make<{ paths: Record<string, unknown> }>({
>   method: 'rest.documentation.openapi',
>   params: {}
> })
> const tails = Object.keys(doc.getData()?.result?.paths ?? {})
>   .filter(path => path.endsWith('.tail'))
> ```
> See [Discovering v3 methods](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/discovering-v3-methods.md) for what else that document carries.
> For everything else use [`CallList`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-list-rest-api-ver3.md), which reaches the same result by emulating a cursor on an ordinary list method.

```ts
// Basic usage
const response = await $b24.actions.v3.callTail.make({
  method: 'main.eventlog.tail',
  params: {
    select: ['id', 'auditTypeId']
  },
  cursorField: 'id',
  customKeyForResult: 'items'
})
```

### How it differs from CallList

The two walkers reach the same place by different roads, and the difference decides what `filter` may contain.

| | `callList` / `fetchList` | `callTail` / `fetchTail` |
| --- | --- | --- |
| Pagination | **Emulated** — appends `[cursorIdKey, '>', cursor]`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} to your `filter` | **Native** — sends `cursor: { field, value, order, limit }`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}, `filter` forwarded untouched |
| `order` | Reserved by the walker | Yours, through `order: 'ASC' \| 'DESC'`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} |
| `filter` shape | The array form only, because the walker has to extend it | The array form **or** a bare logic group |
| Works on | Any list method | Only a method that implements `tail` |

Because the list walkers have to append a condition to your filter, they can only accept the array form. The tail walkers add nothing, so a bare `FilterV3.or(...)`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} group is a legal whole filter here.

### When to Use CallTailV3.make()

1. **The method publishes a `tail` endpoint** and you want the server's own cursor rather than an emulated one.
2. **Small to medium data volumes**, held in memory at once. For a large export use [`FetchTail`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/fetch-tail-rest-api-ver3.md), which yields page by page.

## Method Signature

```ts-type
make<T = unknown>(
  options: ActionCallTailV3
): Promise<Result<T[]>>
```

### Parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| **`method`** | `string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | Yes | A `restApi:v3` tail method, e.g. `main.eventlog.tail`. Pointing this at a `.list` method does not work — see [Pointing it at a list method](#pointing-it-at-a-list-method). |
| **`params`** | `Omit<TypeCallParamsV3, 'pagination' \| 'order' \| 'cursor' \| 'filter'> & { filter?: TypeFilterV3 \| FilterV3Group }`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | Request parameters. `pagination`, `order` and `cursor` are reserved — the walker drives the cursor and takes the direction from `order` below. `filter` accepts the `restApi:v3` array form or a bare logic group. |
| **`cursorField`** | `string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | The field the server pages by. Default `'id'`. It must be **unique, totally ordered and scalar** — see [Choosing a cursor field](#choosing-a-cursor-field). It must also be readable in the response: an explicit `select` gets it appended automatically, and omitting `select` with a non-default `cursorField` earns a warning. |
| **`order`** | `'ASC' \| 'DESC'`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | Direction of the walk. Default `'ASC'`; lowercase is accepted too. `'DESC'` requires `initialValue` — see below. |
| **`initialValue`** | `number \| string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | The cursor value the first page starts from. Default `0`, which suits an ascending numeric field. **Required for `'DESC'`** — that one the SDK enforces. You also need it for any non-numeric field, and there the SDK does *not* check: the default `0` is a number and would be sent as-is. |
| **`customKeyForResult`** | `string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | The key the rows live under in the response envelope. Default `'items'`. |
| **`requestId`** | `string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | Identifier for tracing, sent as `bx24_request_id`. It deduplicates nothing. |
| **`limit`** | `number`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | Rows per page. Default `50`. **A request, not a guarantee** — each method applies its own maximum, and a short page is not the end of the data. |
| **`maxPages`** | `number`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | Ceiling on pages read. Default `10000`; must be a positive integer, or `JSSDK_ACTION_INVALID_MAX_PAGES` is raised at call time. Reaching it does not discard what was read: the rows come back with `JSSDK_ACTION_MAX_PAGES_EXCEEDED` attached, so check `isSuccess`. |
| **`signal`** | `AbortSignal`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | Cancels the walk, checked before each request. The pages already read come back with `JSSDK_ACTION_ABORTED` attached. |
| **`progress`** | `(p: { pages: number, rows: number }) => void`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} | No | Called after each page with cumulative counts. Counts rather than a percentage: keyset paging never asks for a total. |

### Return Value

`Promise<Result<T[]>>`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} — resolves to a [`Result<T[]>`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}](https://github.com/bitrix24/b24jssdk/blob/main/packages/jssdk/src/core/result.ts){rel="[\"nofollow\"]"} holding every row read.

- `.getData(): T[] | null | undefined`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} — the rows. Nullable by type, which is why the examples below reach for `?.length ?? 0`; a completed walk always sets an array, even an empty one.
- `.isSuccess: boolean`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} — false when a bound was hit or the portal returned a soft error mid-walk; the rows already read are still there. Not every failure arrives this way — see [Error Handling](#error-handling).
- `.getErrorMessages(): string[]`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} — the messages.

## Key Concepts

### Choosing a cursor field

The walk asks for rows strictly past the last value it saw, so the field has to carry the whole ordering by itself. Three requirements:

- **Unique.** The condition is strictly greater, so rows sharing the last row's value across a page boundary are never asked for — this follows from the condition rather than from a measurement. `id` is the safe default for a reason.
- **Scalar.** A number or a string. The walk reads the value straight off the response, so an object- or array-valued field cannot serve as a cursor.
- **Readable.** The value has to come back, or there is nothing to advance by. The SDK appends the field to an explicit `select` for you; with no `select` and a non-default field it warns instead, because it cannot know the server's default field set.

> [!CAUTION]
> **Getting this wrong truncates the result silently.** None of the three raises an error. A cursor field that is missing from the response, or is not a scalar, is treated as *no cursor*: the walk stops there and resolves **successfully** with the pages it already had. A non-unique field is worse still — it loses individual rows at page boundaries and nothing marks the gap.
> The only signal is a `warning`-level log line, and only when the page was full. So if a walk returns a suspiciously round number of rows — exactly one page, say — check `cursorField` before you believe it.

For a non-numeric field, pass `initialValue` as well: the default is the number `0`, and the SDK does not check this.

### The cursor field must not appear in `filter`

The server orders and pages by that field, and refuses a filter on it. Measured on `main.eventlog.tail`:

```json
{ "error": { "code": "BITRIX_REST_V3_EXCEPTION_INVALIDFILTEREXCEPTION",
             "message": "Cursor field id cannot be used…" } }
```

The SDK warns before the request rather than letting you find out from the portal:

```text
callTail.make: the cursor field "id" must not appear in `filter` — the server
orders and pages by it and will reject a filter on the same field
(INVALIDFILTEREXCEPTION). Remove it from `filter`.
```

The check descends into logic groups, so a field buried inside `FilterV3.or(...)`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} is found too.

### The cursor field is added to `select`

The walk advances by reading `cursorField` off the last row, so the field has to come back. If you pass `select`, the SDK appends the cursor field when it is missing. If you pass no `select` at all and the cursor field is not the default `id`, it warns instead — it cannot know the server's default field set.

### `order: 'DESC'` requires `initialValue`

A descending walk pages by `field < value`, so the default first value of `0` would match nothing. The SDK refuses it at call time rather than returning a silently empty result:

```ts
// Throws JSSDK_CORE_B24_CALL_TAIL_DESC_REQUIRES_INITIAL_VALUE
await $b24.actions.v3.callTail.make({
  method: 'main.eventlog.tail',
  cursorField: 'id',
  order: 'DESC',
  customKeyForResult: 'items'
})
```

Pass a value that sorts after every row. For a numeric field that is the type maximum; for a datetime field, a far-future ISO-8601 timestamp **stating a zone** (the backwards-cursor guard only compares datetimes that state the same zone, so a mismatch quietly turns the guard off); for any other string, a value that sorts last.

```ts
const response = await $b24.actions.v3.callTail.make({
  method: 'main.eventlog.tail',
  cursorField: 'id',
  order: 'DESC',
  initialValue: Number.MAX_SAFE_INTEGER,
  customKeyForResult: 'items'
})
```

## Error Handling

| Code | When |
| --- | --- |
| `JSSDK_ACTION_V3_TAIL_FILTER_INVALID` | `filter` was the `restApi:v2` object dialect, e.g. `{ '>id': 100 }`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}. Refused here rather than one round trip later, where the portal reports it in wording that never names the dialect. |
| `JSSDK_CORE_B24_CALL_TAIL_DESC_REQUIRES_INITIAL_VALUE` | `order: 'DESC'`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} without `initialValue`. |
| `JSSDK_ACTION_CURSOR_STALLED` | The cursor came back equal to the one just sent — the server is answering with the same page. On a tail walk the usual causes are a `cursorField` that is not the field the server pages by or whose values are not unique, and a method that does not implement `tail` at all. |
| `JSSDK_ACTION_CURSOR_WENT_BACKWARDS` | The cursor moved into a value the walk had already passed. With `order: 'DESC'`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}, check that `initialValue` is the newest value rather than the oldest. |
| `JSSDK_ACTION_MAX_PAGES_EXCEEDED` | The page ceiling was reached. The rows read are returned with the error attached — check `isSuccess`, do not assume a returned list is whole. |
| `JSSDK_ACTION_INVALID_MAX_PAGES` | `maxPages` was not a positive integer. Raised before any request. |
| `JSSDK_ACTION_ABORTED` | The `signal` fired. Handled like the ceiling: what was read comes back with the error attached. |

**Two of these come back, five reject.** Only the last two are bounds you set, so the rows already collected are correct and are handed back with the error attached. The other four — a refused filter, a missing `initialValue`, a stalled or backwards cursor — reject the promise, because there is no result worth keeping. So `isSuccess` alone is not enough:

```ts
try {
  const response = await $b24.actions.v3.callTail.make({
    method: 'main.eventlog.tail',
    cursorField: 'id',
    customKeyForResult: 'items'
  })

  // Reached the ceiling or was aborted: the rows are here and are correct.
  if (!response.isSuccess) {
    console.warn(response.getErrorMessages(), response.getData()?.length ?? 0)
  }
}
catch (error) {
  // Filter refused, a bad `maxPages`, DESC without `initialValue`, or the
  // cursor stopped advancing.
  console.error(error)
}
```

A soft error from the portal mid-walk also lands on the `Result` rather than throwing, so `isSuccess` covers that case too.

See [Error codes and handling](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/errors.md) for the full table.

### Pointing it at a list method

A list method does not implement the cursor, and — measured on `main.eventlog.list` — the portal **ignores the `cursor` parameter silently** rather than refusing it. On that method an unknown top-level key was ignored the same way, so nothing in the response says the walk is not paging. Whether every v3 list method behaves like that was not measured; one that does is enough to matter, because the symptom is indistinguishable from a working walk until the pages start repeating.

Before the cursor guards existed, that walk repeated its first page for ever. Now it stops:

```ts
// Throws JSSDK_ACTION_CURSOR_STALLED — main.eventlog.list has no tail
await $b24.actions.v3.callTail.make({
  method: 'main.eventlog.list',
  cursorField: 'id',
  customKeyForResult: 'items'
})
```

If you meant to walk a list method, use [`CallList`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-list-rest-api-ver3.md).

## Examples

### Reading an event log, oldest first

```ts
const response = await $b24.actions.v3.callTail.make<{ id: number }>({
  method: 'main.eventlog.tail',
  params: { select: ['id', 'auditTypeId'] },
  cursorField: 'id',
  customKeyForResult: 'items',
  limit: 100
})

if (!response.isSuccess) {
  console.warn(response.getErrorMessages())
}

console.log(`rows: ${response.getData()?.length ?? 0}`)
```

### Filtering with a bare logic group

The list walkers require the array form because they extend it. Tail forwards the filter untouched, so a group on its own is accepted:

```ts
import { FilterV3 } from '@bitrix24/b24jssdk'

const response = await $b24.actions.v3.callTail.make({
  method: 'main.eventlog.tail',
  params: {
    filter: FilterV3.or(
      ['auditTypeId', '=', 'MAIN_EVENTLOG'],
      ['auditTypeId', '=', 'MAIN_LOGIN']
    ),
    select: ['id', 'auditTypeId']
  },
  cursorField: 'id',
  customKeyForResult: 'items'
})
```

### A complete script

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

const $b24 = B24Hook.fromWebhookUrl('https://your-portal.bitrix24.com/rest/1/SECRET')

try {
  const response = await $b24.actions.v3.callTail.make<{ id: number }>({
    method: 'main.eventlog.tail',
    params: { select: ['id', 'auditTypeId'] },
    cursorField: 'id',
    customKeyForResult: 'items'
  })

  if (!response.isSuccess) {
    console.warn(response.getErrorMessages())
  }

  console.log(`rows: ${response.getData()?.length ?? 0}`)
}
catch (error) {
  if (error instanceof SdkError) {
    console.error(`the walk could not run: ${error.code}`)
  }
  throw error
}
```

An empty result is an ordinary answer, not a failure: `isSuccess` is `true` and the array is empty. Note that a wrong `customKeyForResult` looks exactly the same — the walker treats a key it cannot find as "no data" — so a silently empty walk is worth checking the key for first.

## Alternatives and Recommendations

- [`FetchTail`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/fetch-tail-rest-api-ver3.md) — the same walk as an async generator, for datasets too large to hold at once.
- [`CallList`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""}](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-list-rest-api-ver3.md) — for the ordinary list methods, which is nearly all of them.
- [Filtering](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/filtering.md) — the filter dialects and which walker accepts which.

## Sitemap

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