We are still updating this page. Some data may be missing here — we will complete it shortly.
Goal
Pull every CRM deal closed in a date window from a Bitrix24 portal and write it into a CSV file. Memory stays flat regardless of how many deals match — FetchListV2.make() streams pages of 50 records at a time.
Stack & Prerequisites
- Node.js 20+, ESM (
"type": "module"inpackage.json) @bitrix24/b24jssdk@^1.0.0- A Bitrix24 inbound webhook URL with the
crmscope. Set it asB24_HOOKin your environment.
pnpm add @bitrix24/b24jssdk
export B24_HOOK="https://your-portal.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/"
Full Example
scripts/deals-to-csv.mjs:
import { writeFile } from 'node:fs/promises'
import {
B24Hook,
EnumCrmEntityTypeId,
ParamsFactory,
Text
} from '@bitrix24/b24jssdk'
const hookUrl = process.env.B24_HOOK
if (!hookUrl) {
console.error('B24_HOOK is not set')
process.exit(1)
}
// Closed in the previous calendar month.
const now = new Date()
const from = new Date(now.getFullYear(), now.getMonth() - 1, 1)
const to = new Date(now.getFullYear(), now.getMonth(), 1)
const b24 = B24Hook.fromWebhookUrl(hookUrl, {
// Lower per-second pressure than `getDefault()` — good for reporting jobs
// that run alongside live user traffic on the same portal.
restrictionParams: ParamsFactory.getBatchProcessing()
})
const generator = b24.actions.v2.fetchList.make({
method: 'crm.item.list',
params: {
entityTypeId: EnumCrmEntityTypeId.deal,
filter: {
'>=closedAt': Text.toB24Format(from),
'<closedAt': Text.toB24Format(to),
stageSemanticId: 'S' // closed-won
},
select: ['id', 'title', 'opportunity', 'currencyId', 'closedAt', 'assignedById']
},
idKey: 'id',
customKeyForResult: 'items',
requestId: 'deals-to-csv'
})
const rows = ['id;title;amount;currency;closedAt;assignedById']
let count = 0
for await (const chunk of generator) {
for (const deal of chunk) {
rows.push([
deal.id,
JSON.stringify(deal.title ?? ''),
deal.opportunity ?? '',
deal.currencyId ?? '',
deal.closedAt ?? '',
deal.assignedById ?? ''
].join(';'))
}
count += chunk.length
process.stderr.write(`fetched: ${count}\r`)
}
const file = `deals-${from.toISOString().slice(0, 7)}.csv`
await writeFile(file, rows.join('\n'), 'utf8')
console.log(`\nwrote ${count} rows to ${file}`)
Run It
node scripts/deals-to-csv.mjs
# fetched: 4350
# wrote 4350 rows to deals-2026-04.csv
How It Works
B24Hook.fromWebhookUrl()parses the webhook URL and gives you a configuredTypeB24instance — noawaitinitialization needed.ParamsFactory.getBatchProcessing()swaps the default rate-limit profile for one tuned for long-running scans, so the script doesn't fight live UI requests for the portal queue.actions.v2.fetchList.make()returns anAsyncGenerator<T[]>. Eachfor awaititeration yields up tolimitrecords (default50) and uses cursor-based pagination onidKeyascending — the SDK takes care of stitching pages together.Text.toB24Format(date)converts a JSDateto the ISO format Bitrix24 expects incrm.item.listfilters; passing a rawDatewould not work.
Limitations
crm.item.listreturns up to 50 records per page; this is a server-side limit, not an SDK setting.- The generator does not support a custom
orderparameter — cursor pagination requires sorting byidKeyascending. Specifyingorderinparamstriggers a runtime warning and is stripped. idKeydefaults to'ID'(uppercase) for v2 endpoints. CRM item methods returnid(lowercase), so override it explicitly as in the example.- One
B24Hookinstance corresponds to a single user account on the portal. If the deals you need are owned by another user, the webhook must be created by that user (or by an account with cross-user access). - For very large windows that exhaust the per-day method-call budget, split the date range into smaller chunks and run them sequentially.
See also
FetchListV2.make()— full method reference.- Node.js install +
B24Hook— webhook-based authentication for server scripts like this one. - Restrictions System — choosing a
restrictionParamspreset.
CRM analytics
Stream all deals via actions.v2.fetchList.make, group by stage, print a funnel report (counts, conversion %, avg ticket, win rate).
Error handling
Demonstrates the four error layers (SdkError / AjaxError / network / soft) and the hardErrorCodes / softErrorCodes / retryOnNetworkError knobs on the restriction manager.