---
title: "FetchTailV3.make"
description: "Async generator that streams 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/fetch-tail-rest-api-ver3"
last_updated: "2026-10-01"
---
# FetchTailV3.make

> Async generator that streams all records of a REST API version 3 tail method through the server's native keyset cursor.

## Overview

`FetchTailV3.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 through the server's native keyset cursor and yields each page as it arrives, so a dataset larger than memory can be processed a page at a time.

It is the streaming counterpart of [`CallTail`{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-tail-rest-api-ver3.md); everything about the cursor, the filter shapes and the errors is the same, and that page documents it in full. What follows is what differs.

> [!CAUTION]
> **Check that the method exists first.** A tail endpoint is separate from its list counterpart and few modules publish one — see [how to ask the portal](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-tail-rest-api-ver3.md#overview). For an ordinary list method use [`FetchList`{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-list-rest-api-ver3.md).

```ts
for await (const page of $b24.actions.v3.fetchTail.make<{ id: number }>({
  method: 'main.eventlog.tail',
  params: { select: ['id', 'auditTypeId'] },
  cursorField: 'id',
  customKeyForResult: 'items',
  limit: 100
})) {
  // `page` is one server page, already parsed
  console.log(`Processing ${page.length} items`)
}
```

### When to Use FetchTailV3.make()

1. **Large exports and migrations** — rows are handed over and can be released, instead of accumulating.
2. **Early exit** — `break` out of the loop and no further request is made.
3. **Progress that means something** — you see each page as it lands.

Use [`CallTail`{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-tail-rest-api-ver3.md) instead when the whole result fits in memory and one array is simpler to work with.

## Method Signature

```ts-type
make<T = unknown>(
  options: ActionFetchTailV3
): AsyncGenerator<T[]>
```

The options are those of [`CallTail`{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-tail-rest-api-ver3.md#parameters): `method`, `params`, `cursorField`, `order`, `initialValue`, `customKeyForResult`, `requestId`, `limit`, `maxPages` and `signal`.

There is no `progress` callback here, and none is needed — the loop body runs once per page, which is the same signal a callback would carry.

Two rules from that page apply here unchanged and are easy to miss coming from the list walkers: the **cursor field must not appear in `filter`** (the server refuses it), and `filter` may be the array form **or a bare `FilterV3` logic group** — the list walkers accept only the array, because they have to extend it. See [Choosing a cursor field](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-tail-rest-api-ver3.md#choosing-a-cursor-field) for what the field itself has to be.

### Return Value

`AsyncGenerator<T[]>`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" language="ts-type" style=""} — yields one array per server page. **Nothing happens until you consume it**: the first request is made on the first iteration, so a generator that is created and never iterated sends no traffic at all. That is also where every guard fires — wrapping only the `make(...)` call in a `try` catches nothing.

An empty result yields **no page at all**, so the loop body never runs. A wrong `customKeyForResult` looks identical, because a key the walker cannot find reads as "no data" — check the key before concluding the portal had nothing.

## Error Handling

Everything here **throws**, which is the difference from `callTail` worth writing code around.

| Code | When |
| --- | --- |
| `JSSDK_ACTION_V3_TAIL_FILTER_INVALID` | `filter` was the `restApi:v2` object dialect. Raised on the first iteration, before any request. |
| `JSSDK_CORE_B24_FETCH_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`. Note the `FETCH_TAIL` segment: `callTail` raises the `CALL_TAIL` spelling, so match the exact string. |
| `JSSDK_CORE_B24_FETCH_TAIL_METHOD_API_V3` | The portal returned a soft error mid-walk. This is the one you will see most often, and it is the real asymmetry: `callTail` folds the same failure into its `Result` instead. |
| `JSSDK_ACTION_CURSOR_STALLED` | The cursor came back equal to the one just sent. Usually a `cursorField` that is not the field the server pages by, or a method with no `tail` endpoint at all. |
| `JSSDK_ACTION_CURSOR_WENT_BACKWARDS` | The cursor moved into a value already passed. With `DESC`, check `initialValue` is the newest value. |
| `JSSDK_ACTION_MAX_PAGES_EXCEEDED` | The page ceiling was reached. |
| `JSSDK_ACTION_INVALID_MAX_PAGES` | `maxPages` was not a positive integer. Raised on the first iteration, before any request. |
| `JSSDK_ACTION_ABORTED` | The `signal` fired. |

### Where this really differs from CallTail

Three of these reach you differently:

- `JSSDK_ACTION_MAX_PAGES_EXCEEDED` and `JSSDK_ACTION_ABORTED` are bounds *you* set, so `callTail` hands back the rows it collected with the error attached. A generator cannot — it has already yielded every page it read;
- a soft portal error is reported by `callTail` on the `Result` and here as `JSSDK_CORE_B24_FETCH_TAIL_METHOD_API_V3`.

The rest throw from **both** walkers: `await callTail.make(...)` without a `try` rejects on a stalled cursor exactly as the loop below does.

`callTail` resolves a `Result` and attaches a bound-related error to it, so the rows it did read survive. A generator cannot do that — it has already handed over every page it read — so it **throws** instead:

```ts
try {
  for await (const page of $b24.actions.v3.fetchTail.make({
    method: 'main.eventlog.tail',
    cursorField: 'id',
    customKeyForResult: 'items'
  })) {
    console.log(`Processing ${page.length} items`)
  }
}
catch (error) {
  // The pages already yielded have been processed. If that is not safe for your
  // consumer, undo them here — see the note below.
  throw error
}
```

> [!CAUTION]
> **A page that was yielded was already delivered.** If the walk stops with `JSSDK_ACTION_CURSOR_STALLED` or `JSSDK_ACTION_CURSOR_WENT_BACKWARDS`, the pages your loop already processed may contain duplicates — the server was repeating itself. A consumer that persisted them has to undo that. This is the opposite of `JSSDK_ACTION_MAX_PAGES_EXCEEDED` and `JSSDK_ACTION_ABORTED`, where what was read is correct and merely incomplete.

## What else differs from CallTail

### Stopping early

Leaving the loop stops the walk; no further page is requested.

```ts
let seen = 0

for await (const page of $b24.actions.v3.fetchTail.make<{ id: number }>({
  method: 'main.eventlog.tail',
  cursorField: 'id',
  customKeyForResult: 'items'
})) {
  seen += page.length
  if (seen >= 500) {
    break
  }
}
```

For a bound the SDK enforces rather than one you check yourself, pass `maxPages` or a `signal`.

## Examples

### Streaming an export, oldest first

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

let pages = 0
let rows = 0

try {
  for await (const page of $b24.actions.v3.fetchTail.make<{ id: number }>({
    method: 'main.eventlog.tail',
    params: { select: ['id', 'auditTypeId'] },
    cursorField: 'id',
    customKeyForResult: 'items',
    limit: 100
  })) {
    rows += page.length
    console.log(`page ${++pages}, ${rows} rows`)
  }
}
catch (error) {
  if (error instanceof SdkError) {
    console.error(`walk stopped: ${error.code}`)
  }
  throw error
}
```

### Newest first

A descending walk needs an explicit starting point, because the server pages by `field < value`:

```ts
for await (const page of $b24.actions.v3.fetchTail.make<{ id: number }>({
  method: 'main.eventlog.tail',
  cursorField: 'id',
  order: 'DESC',
  initialValue: Number.MAX_SAFE_INTEGER,
  customKeyForResult: 'items'
})) {
  console.log(`Processing ${page.length} items`)
}
```

Without `initialValue` the walk raises `JSSDK_CORE_B24_FETCH_TAIL_DESC_REQUIRES_INITIAL_VALUE` rather than walking away empty. `Number.MAX_SAFE_INTEGER` suits a numeric field; for a datetime or any other string, see [what to pass instead](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-tail-rest-api-ver3.md#order-desc-requires-initialvalue). It fires on the first iteration, not when the generator is created — nothing runs until you consume it.

## Alternatives and Recommendations

- [`CallTail`{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-tail-rest-api-ver3.md) — the same walk collected into one array, and where the shared rules live: [choosing a cursor field](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-tail-rest-api-ver3.md#choosing-a-cursor-field) and [a complete script](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/call-tail-rest-api-ver3.md#a-complete-script).
- [`FetchList`{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-list-rest-api-ver3.md) — the streaming walker for ordinary list methods.
- [Filtering](https://bitrix24.github.io/b24jssdk/raw/docs/working-with-the-rest-api/filtering.md) — which filter shapes each walker accepts.

## Sitemap

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