SuperAPI AI Directory

b24jssdk-recipes

Data & analytics

View repository

Updated 10-15 20:06

About this skill

b24jssdk recipes

Thirteen end-to-end programs. Every recipe runs on B24Hook (Node.js), but each function body takes $b24: TypeB24 so the same code works in-frame too — just swap the boot for initializeB24Frame(). (Recipe 13 is the exception until a browser is verified: its result file is downloaded from the portal, see the DeferredBatch guide.)

All recipes use the canonical $b24.actions.v{2,3}.*.make() surface. The legacy callMethod / callBatch / callListMethod / fetchListMethod was removed in 3.0.0 — do not generate code against it.

# File Stack Scopes What it does
1 examples/01-crm-analytics.ts Node crm Stream all deals via actions.v2.fetchList.make, group by stage, print a funnel report (counts, conversion %, avg ticket, win rate)
2 examples/02-mass-messaging.ts Node crm, im Filter contacts via actions.v2.call.make, send im.notify to assigned managers
3 examples/03-task-automation.ts Node, setInterval crm, task Poll deal stages with actions.v2.fetchList.make; on watched transition create a task via actions.v3.call.make('tasks.task.add', …)
4 examples/04-erp-sync.ts Node, node-cron crm Two-way contact sync between Bitrix24 (via actions.v2.*) and a mock ERP
5 examples/05-disk-files.ts Node disk Storages → root → create folder → list files, with a actions.v2.batch.make round-trip
6 examples/06-telegram-bot.ts Node, grammy, node-cron crm Poll new deals via actions.v2.call.make, notify a Telegram chat
7 examples/07-webhook-handler.ts Node, express crm, task Express server that receives Bitrix24 outbound events; loads details with actions.v2.call.make, then creates a follow-up task with an idempotencyKey so a redelivered event does not create it twice
8 examples/08-ai-assistant.ts Node, openai crm, task Deal + activity timeline → GPT prompt → actions.v3.call.make('tasks.task.add') follow-up
9 examples/09-web-search-llm.ts Node, BYOC crm Two-step RAG; SDK posts the answer to a deal's timeline via actions.v2.call.make('crm.timeline.comment.add')
10 examples/10-error-handling.ts Node any Error-handling cookbook: AjaxError vs SdkError taxonomy; hardErrorCodes / softErrorCodes / retryOnNetworkError knobs via setRestrictionManagerParams; non-idempotent-call safety, with idempotencyKey as the v3 answer
11 examples/11-event-registration.ts Node crm CLI tool — list / bind / unbind outbound webhook events (event.get, event.bind, event.unbind). Pairs with recipe 7.
12 examples/12-oauth-install.ts Node, express OAuth app OAuth install handshake: handle ONAPPINSTALL / ONAPPUPDATE / ONAPPUNINSTALL events, persist tokens per portal, build B24OAuth on demand, refresh callback writes new tokens back to storage
13 examples/13-deferred-batch-export.ts Node task Bulk export of thousands of tasks (list pages, since one failing command fails the job) as ONE background job via actions.v3.deferredBatch — make() with an onStatus progress callback, or the add → waitFor → download → delete steps across two runs; writes JSON Lines

Shared library (lib/)

Pure, I/O-free helpers extracted from recipes so they can be unit-tested without a live portal.

lib/ is for helpers where a second hand-written copy is a defect — a security primitive, or logic with edge cases a test pins. It is not for shared plumbing: these are recipes, and every extraction costs a reader the ability to lift one file and run it. When in doubt, leave the code in the recipe.

Worked example of the rule: bootB24() is byte-identical in twelve of the thirteen recipes and stays that way on purpose. It has no edge cases to pin and no security weight, and it is the first thing a reader needs to see when they open a recipe — hiding it behind an import would cost more than the duplication does.

Recipes 06 and 12 end with a guard instead of a bare main() call:

import { pathToFileURL } from 'node:url'

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  main().catch((e: unknown) => {
    // Raw console.error, so structured-logger formatting cannot hide the trace.
    console.error('\n[recipe failed]', e instanceof Error ? `${e.name}: ${e.message}` : String(e))
    process.exitCode = 1
  })
}

npx tsx 06-telegram-bot.ts still runs exactly as before — the guard is only false when the file is imported, which is what the unit tests do to exercise tick's cursor logic and the uninstall token check without opening a Telegram connection or binding a port. Keep it when copying those two recipes. Before adding it to a third, prefer the lib/ route above: extract the logic and test it there. Reach for the guard only when the function cannot reasonably leave the recipe — tick closes over a live bot and the poll cursor, handleUninstall is an Express handler — because two shapes of recipe is already one more than ideal, and the guard spreading to all thirteen would quietly retire the lib/ convention. The exported functions in those files are exported for the same reason, and export is inert when the file runs directly.

One deployment caveat: the guard compares import.meta.url against process.argv[1]. Under an exotic launcher that rewrites either — a wrapper CLI, some loader shims — the comparison can come out false, and the process would exit 0 having started nothing. Check for the recipe's startup log line rather than trusting the exit code.

File Exports Used by
lib/funnel.ts baseStage, analyseFunnel, DealRow, StageStat recipes 01, 03, 06
lib/crypto.ts safeEqual recipes 07, 12
lib/portal-url.ts checkPortalUrls recipe 12

baseStage(s) strips the multi-funnel category prefix ("C2:WON" → "WON"), falling back to the original string when the part after the colon is empty ("C2:" → "C2:"). Import it from lib/funnel rather than re-declaring it in a recipe: 03 and 06 each carried their own copy that returned "" for that case. No recipe's control flow actually differed — neither "" nor "C2:" matches a stage key — but the copies had drifted from the tested one unnoticed, and the next divergence might not be harmless.
analyseFunnel(deals) groups deals by raw stageId key, summing counts and opportunity amounts.

safeEqual(a, b) is a constant-time compare for secrets and tokens. It had the same two-copy problem as baseStage, in recipes 07 and 12. Never re-declare this one: a constant-time compare that drifts in one of two forks is a vulnerability that reads like a refactor, and === on a token is the bug it exists to prevent.

Boot snippet (shared by all recipes)

import { B24Hook, ConsoleV2Handler, LogLevel, Logger, type TypeB24 } from '@bitrix24/b24jssdk'

export function bootB24(): TypeB24 {
  const url = process.env.B24_HOOK
  if (!url) throw new Error('B24_HOOK env var is required (incoming webhook URL)')
  const $b24 = B24Hook.fromWebhookUrl(url)
  return $b24
}

// Recipes run under Node, so the console handler is created explicitly with
// `useStyles: false` — the browser factory emits ANSI-free CSS styling that a
// terminal prints as literal noise.
export const logger = Logger.create('Recipe')
logger.pushHandler(new ConsoleV2Handler(
  process.env.NODE_ENV === 'production' ? LogLevel.ERROR : LogLevel.INFO,
  { useStyles: false }
))

The snippet is inlined into each recipe file for copy-paste convenience.

API-version split — quick reference

Inside the recipes the split is:

Method Action surface Why
crm.item.{get,list,add,update,delete} actions.v2.* Classic API used via v2 here
crm.activity.list, crm.timeline.comment.add actions.v2.* Classic API, v2 only
tasks.task.{add,get,update,delete,list} actions.v3.* Used via v3 (camelCase, result.items)
disk.*, im.*, profile, user.* actions.v2.* Classic API, v2 only
main.eventlog.{list,get,tail} actions.v3.* v3, incl. native tail

The SDK no longer gates v3 by a hardcoded allowlist: actions.v3.* sends any method to the v3 endpoint and the server validates it (an unknown method returns METHODNOTFOUNDEXCEPTION as a soft error on the AjaxResult, not an SDK throw). The recipes pick a surface per method by what reads cleanest for that data, not by a whitelist.

Field-naming choice

The newer CRM methods (crm.item.list) use camelCase fields: stageId, assignedById, opportunity, createdTime. The classic ones (tasks.task.add, crm.activity.list, crm.timeline.comment.add) use uppercase: TITLE, DESCRIPTION, OWNER_TYPE_ID. Recipes follow this split. Both are restApi:v2 — crm.item.* is camelCase for reasons of its own, not because it is v3; the v3 endpoint publishes no crm.item.*. Under restApi:v3 camelCase is the rule everywhere, and <entity>.field.list answers it per entity — see Discovering entity fields.

Running

# 1. Install: at the repo root or in a fresh project
pnpm add @bitrix24/b24jssdk
# Copying a single recipe out of this repo? Some recipes import a shared
# helper and need it copied alongside, keeping the ../lib/ path:
#   recipes 1, 3, 6:  lib/funnel.ts  (baseStage)
#   recipes 7, 12:    lib/crypto.ts  (safeEqual)
# Every other recipe is standalone.
# Recipe-specific deps as needed:
#   recipe 4: pnpm add node-cron
#   recipe 6: pnpm add grammy node-cron
#   recipe 7: pnpm add express
#   recipe 8: pnpm add openai
#   recipe 9: pnpm add openai
#   recipe 12: pnpm add express
# Recipe 12 also honours B24_OAUTH_STORE — where to write the OAuth token
# store. Defaults to .oauth-store.json in the working directory, which is
# rarely where portal credentials should live on a real server.

# 2. Set env
export B24_HOOK='https://YOUR_PORTAL.bitrix24.com/rest/1/k32t88gf3azpmwv3'

# 3. Run with tsx (no build step)
npx tsx examples/01-crm-analytics.ts

Caveats applied across recipes

  • Multi-funnel pipelines: stage IDs may carry a category prefix (C2:WON, C4:LOSE). Recipes 1, 3, and 6 strip the prefix when checking the base stage.
  • order in callList.make: silently dropped (the action forces cursorIdKey ASC, defaulting to idKey, for cursor stability). Use filter to narrow.
  • customKeyForResult: 'items' for crm.item.list, omit or 'tasks' for classic methods. Wrong value → silent empty array.
  • idKey / cursorIdKey: idKey: 'id' for crm.item.list; 'ID' (default) for classic methods. tasks.task.list is the exception on v2 — it returns lowercase id but sorts by ID, so use idKey: 'id', cursorIdKey: 'ID' (and customKeyForResult: 'tasks'); on v3 it is all-lowercase: idKey: 'id' (default), no cursorIdKey, customKeyForResult: 'items'.
  • Error handling: failed calls throw AjaxError for REST errors; recipes log and continue where it makes sense. Tune via setRestrictionManagerParams if you need different retry behaviour (see b24jssdk-core).
  • Webhook events (recipe 7): registration of the outbound webhooks themselves (which events go where) is a one-off setup. Recipe 11 (11-event-registration.ts) is a small CLI for that: list / bind / unbind via event.get / event.bind / event.unbind.

Cross-reference

For v3 wire-level details (filter grammar, batch $ref/$refArray, cursor pagination), use the b24jssdk-rest and b24jssdk-filtering skills.