Bitrix24
Changelog
Read Bitrix24 CLI release notes, including new capabilities, changes and fixes.
Notable changes to @wirecat/bitrix24-cli. One section per version, newest first.
0.14.0 — 12.11.2026
Added
b24 guidelists scenarios for common tasks, each as commands in order with why:first-sync(the first sync and the pipeline board),keep-in-sync(a timer and search) andsafe-agent-writes(an agent over MCP whose writes stay yours).b24 guide <name> --jsongives one as data.- MCP and Permissions pages. How to add
b24to an agent, what the agent sees and what it is never offered; each level, the built-in levels, the keys one command checks, and the checks no level lifts. - Sign-in through a local application.
b24 <profile> session start --portal <host> --client-id <id>signs in through a local application you create in the portal, in the browser, and acts with the rights of the employee who signs in. The application key is asked without showing it, or read from stdin orB24_BITRIX24_CLIENT_SECRET. The sign-in returns throughhttps://wirecat.dev/webhooks/apps/b24-callback, or the address you paste. Tokens are renewed by themselves.session statusandsession listshow the kind of sign-in, andsession endforgets either. The incoming webhook stays the quick way; see sign in.
Fixed
- The quick start copies deals. The README now chooses whose deals and leads to copy before the
first
sync, which copies nothing until then. It no longer says an administrator copies the whole CRM, says that over MCP a delete set toaskruns, and nameswcat-b24for when another package'sb24command is found first. Troubleshooting has the "no CRM user is chosen" error. - Over MCP,
askdoes not stop a write. The README, security and the first screen told owners to setdeals.movetoaskagainst the portal's robots, which stops a move only at a terminal: over MCP nobody answers, so the write goes ahead. They now say to setreadonlyordeny, or to turn on approval in the agent's client. - The agent skill covers what an agent needs.
b24 skill shownow has exit codes and error reasons, MCP and theasktrap, the first setup from the webhook to the first sync, the boundaries of a write, and the traps. - The examples use the profile
workthroughout, in place ofpersonal. b24 commandslists the CRM by section. Deals, leads, contacts and companies split into Read and Write, pipelines, stages, fields, products, users andapiare under Read,syncunder Automate andsessionunder Set up; before, most ofb24was under Other.- The two local state files are written atomically and stay private. The list of your own companies and the day's count of other people's clients shown now go through a temporary file and a rename, as the settings file does.
- A daily
syncno longer reads every client again. A round of changes read every contact and company named on the chosen users' deals and leads, whether or not anything changed: on a portal with 50,000 deals that was about 300 requests and close to three minutes for a sync with nothing new. It now reads only the clients of the deals and leads that changed, and lists the users' deals for their clients once a sync instead of once for contacts and once for companies.
0.13.0 — 11.11.2026
Changed — may break callers
- Only a Bitrix24 administrator's webhook deletes or clears in the CRM. For any other webhook,
b24refusesdeals delete,leads delete, anapi callto a method that deletes (its name ends in.delete,.deletefieldvalueor.markdeleted), an update or.setthat sets a field to nothing (an empty value,null,[], a0company, contact or lead link, or a phone or e-mail markedDELETE: Y), andbatch. The refusal is apermission_errorwith reasonemployee.delete,employee.clearoremployee.batch, and no permission level lifts it. A create may still leave fields empty. - Only a Bitrix24 administrator's webhook unlinks records or merges duplicates. For any other webhook,
api callrefuses a method whose name ends in.unbind, such ascrm.timeline.bindings.unbind, with reasonemployee.unbind, and a merge of duplicates,crm.entity.mergeBatch, which deletes all but one of the records, with reasonemployee.merge. No permission level lifts it, as for deletes.crm.deal.contact.deleteandcrm.deal.contact.items.deletealready counted as deletes. - A portal's account is stored as work. The first
sync, write or read of a profile stores a new portal's account with scopework, so work views and meeting preparation read its copy and personal reads leave it out. A portal already in the store keeps the scope it has, which ispersonalin every store so far: switch it once withmemo groups update bitrix24:<portal> --scope work(memo 0.131.81 or later). A sync never changes a scope back.
0.12.0 — 10.11.2026
Added
store applyremoves, from a copy made underscope mine, the contacts that no deal or lead of the chosen users names and that belong to no kept company, and the people they leave empty. Without--yesit only counts. A sync that narrows the copy, orsync --rescan, does this itself, and its answer gives the counts ascontacts_removed.- Built on cli-core 0.631.0 (was 0.630.0). The store moves to version 64 on first open: saving or removing a deal's link to a person no longer reads every such link. What changed is in cli-core's changelog.
Changed — may break callers
- A sync copies only the deals and leads of the users the owner chooses in
crm.bitrix24:<portal>.unassigned, and their clients. No one is chosen by default, the webhook's own user included: until the owner chooses, a sync is refused (configuration_error, reasonbitrix.scope-users-unchosen) and names theconfig setto run. Watch for: an existing copy fails its next sync until the owner chooses. - No sync copies the whole CRM any more, an administrator's either: a sync under a stored
scope allis refused (reasonbitrix.scope-all-refused, wasbitrix.scope-all-admin-only) and says to setmine. Once set, deals and leads of users not chosen are marked deleted in the local copy. Find any other client live on the portal withpeople listororganizations list. - An employee's webhook may choose themselves, a dismissed user or a non-employee; an administrator's, anyone.
Fixed
- A contact's name no longer carries the
nullornull #<id>a portal import left in its name fields ("Ярослав Орлов null #907" is now "Ярослав Орлов"). 101,876 of 114,165 contacts on one live portal had it. Watch for: a name stored before keeps the leftover until that contact is synced again. - Built on cli-core 0.630.0 (was 0.623.0). The store moves to version 63 on first open. A CRM contact no longer joins or renames your own person, and two contacts sharing a company's mailbox or phone stay two people. What changed in between is in cli-core's changelog.
0.11.0 — 10.11.2026
Changed — may break callers
- Under
scope minea sync copies only the user's clients: the contacts and companies assigned to them or to an unassigned user, and the ones named on the deals and leads it copies. Before, it copied the portal's whole contact and company base, 114 165 contacts on one live portal where the user held no deal. The daily deletion pass lists only those ids too. Watch for: a copy that already holds the whole base keeps its contacts; companies wait forsync --rescan. - Only a Bitrix24 administrator may sync under
scope all. A department head is refused like an employee (bitrix.scope-all-admin-only, wasbitrix.scope-all-manager-only). - Anyone but a Bitrix24 administrator sees phones and e-mails only of their own clients.
people listandorganizations listleave them out of the rest;people showandorganizations showof one give them, at most 100 such contacts and companies a day for the user and their agents together.api callrefuses contact and company reads (crm.contact.*,crm.company.*,crm.item.*of type 3 or 4, requisites, addresses, duplicate searches) to anyone but an administrator, whatever the permissions say. - Only a Bitrix24 administrator may name a working employee in
crm.bitrix24:<portal>.unassigned; a department head is held to dismissed users and non-employees like everyone else. - Built on cli-core 0.623.0 (was 0.621.0). The store moves to version 61 on first open (people ties, additive). What changed in between is in cli-core's changelog.
0.10.0 — 09.11.2026
Added
session startandsession status --checkanswer whether the webhook's user is amanager(a Bitrix24 administrator or the head of a department), besideadmin.
Changed — may break callers
- Only a manager copies every deal and lead: a sync under
crm.bitrix24:<portal>.scope allis refused (configuration_error) unless the webhook's user is a Bitrix24 administrator or the head of a department, checked on every sync. Anyone else copies their own deals and leads and the unassigned ones; the refusal says to setscopetomine. Watch for: a webhook without thedepartmentscope counts a department head as an employee. - For anyone but a manager,
crm.bitrix24:<portal>.unassignedcounts only users the portal shows as dismissed or as not employees (extranet, e-mail, bot), checked at every sync. A working employee in the list is left out with the reason in the sync'sissues(crm.unassigned-dropped), and their deals and leads leave the local copy. A manager may still list anyone. Watch for: a webhook without theuserscope counts no one as unassigned for an employee.
0.9.0 — 09.11.2026
Added
syncstores who is who in the local store: a contact is a member of each of its companies with the rolecontact, its job title beside it; a deal's and a lead's contacts are on it with the main one marked, and the person responsible is itsowner; employees are members of your own companies, read every sync, and report to their department's head; a converted lead points to its deal. These relationships arereporteduntil you confirm them, end instead of being deleted when the portal stops saying them, and never change one you made. Watch for: the webhook needs theuser_basicanddepartmentscopes for employees' e-mails and reporting lines; without them the sync reads the rest.scheduleinstalls a timer that runssync, or anotherb24command, on its own. MCP does not offer it.- A sync copies only the deals and leads of the webhook's own user by default, and those of the users in
crm.bitrix24:<portal>.unassigned(Bitrix24 always names a responsible person, so this is how a website form's or an integration's deals count as nobody's).config set crm.bitrix24:<portal>.scope allcopies every one. The sync answer hasscope(with the webhook's user asself), and each kind'sout_of_scope: a deal given to another user leaves the local copy at the next sync. Contacts and companies are copied whole as before. - Without a
scopesetting, a firstminethat would mark more than half of the stored deals or leads deleted keeps them (crm.narrowing-held) until the owner setsscope. An agent cannot setscope,selforunassigned. - One portal's copy belongs to one user: a sync through a webhook of another user than the first sync's is refused
until
config unset crm.bitrix24:<portal>.self.
Changed — may break callers
- A deal's and a lead's
peoplenow include the employee responsible for it, with the roleowner, beside its contacts. Watch for: code that took every entry inpeoplefor a client's contact. store statuscounts your own companies among the organizations and the portal's employees among the people.api call department.getandcrm.mycompany.basic.listrun without asking, as the other readsb24makes do;department.getis refused underusers.list: deny, andcrm.mycompany.*underorganizations.list: deny.- A webhook made with only the
crmanduserscopes now gets an entry in the sync'sissueson every sync: Bitrix24 refuses it the departments. Add thedepartmentscope, or expect that entry. - The first sync after the upgrade marks other users' deals and leads deleted in the local copy: they leave lists and
search, and their earlier copy stays in the store file. Set
crm.bitrix24:<portal>.scope allbefore it to keep them. - A list answer without its items now fails that kind's sync (
invalid_response) instead of ending the list there.
Fixed
api show department.getandapi callnamed the method's scopebasic; Bitrix24's isdepartment.- A method that Bitrix24 lets only an installed app call (
WRONG_AUTH_TYPE) is refused aspermission_errorwith the reasonwebhook.app-only. Before,b24said the webhook was refused and asked you to sign in again, which cannot help. Watch for: a script that branched onauthentication_errorfor this refusal. peopleandorganizationscreateandupdatedeclare their answers (crm_record_created,crm_record_updated), so--jsontakes their field names andcommands infolists them.session startand troubleshooting name the same webhook scopes as the README:crm,user_basicanddepartment. Troubleshooting also asked forcatalog, whichb24does not use.api show,api listand a refused call named the scopebasicfor every method outside five name prefixes, such asim.message.addandtasks.task.add. They now name the scope Bitrix24's reference gives (im,task,calendar), orunknown, and a refusal for an unknown one names no scope.
Security
b24sends requests only to the portal in your webhook address and refuses any other host before a byte leaves the machine. Its manifest says so (network), so WireCat Desktop shows where the data goes.
0.8.0 — 08.11.2026
Added
- The package ships
contracts/wirecat.json, so WireCat Desktop can read whatb24does, how to sign in and whatsynccopies before it installs it.b24 --wcat-describenow answers format 2 with that file's Desktop section and its SHA-256.
Fixed
- The first sync of a large portal uses far less memory and takes a third less time: on synthetic data with 20,000 deals, its peak falls from 3.7 GB to 0.55 GB.
session startasks for the webhook again when nothing arrived. An empty Enter at the hidden prompt, as after a Ctrl+V that pasted nothing in a Linux terminal, failed at once with "no webhook address was given". It now says to paste with Ctrl+Shift+V (Cmd+V on macOS) and asks again, up to three times.- A crash while
b24starts, or one an awaited step throws, exits 1 with b24's crash error (command.crashedas JSON when stdout is not a terminal). It printed a raw Node stack.
0.7.0 — 08.11.2026
Added
- The package also installs the command as
wcat-b24, sowcat b24runs it andwcat --helplists it.b24 --wcat-describeprints what the tool is, its version and its docs page. config set owner_languagesnames the languages the owner reads.
Changed — may break callers
- A search with no letter or digit is refused (
validation_error)."", a lone?or an emoji alone found nothing and said nothing. A lone*still reads as a wildcard.
Fixed
- The first sync of a large portal is about twice as fast. It no longer slows down with every deal it saves: on synthetic data with 10 deal owners, 20,000 deals, 10,000 leads and 20,000 contacts sync in 30 s instead of 61 s.
0.6.0 — 08.11.2026
Added
people list --format csvandorganizations list --format csvprint a page as a CSV table for a spreadsheet, asdeals listandleads listnow do too. stdout carries the table and nothing else; the way to the next page goes to stderr. The columns are the record's own fields, then every custom field of the portal by its label, a money field as<label> amountand<label> currency, a label two fields share told apart by its code.--json=a,bbeside it picks the columns, a custom field asprovider_metadata.<code>. A cell that would start a spreadsheet formula gets a leading'.--jsonl, a bare--jsonand--compactare refused withoutput.conflict, and agents do not get the option.pipelines showis the sales funnel board: each stage gives its deals or leads and their total in each currency, and at a terminal prints a table of the stages with the open total, the won, the lost and the win rate. The pipeline may be left out when the portal has only one.
Changed — may break callers
- A custom field the portal does not have, named in a list's query or in
--json, is refused withparamset to--queryor--json, where it said--field. - A command on a profile with no webhook is refused with reason
session.missing, where it saidwebhook.missing: the one "not signed in" every WireCat tool with an API token gives. The webhook is read and kept where it was,B24_TOKEN, then the keyring entrytoken:<profile>underbitrix24-cli, then a private file, now by cli-core's shared token store.
0.5.0 — 07.11.2026
Added
synccopies the portal's pipelines, companies, contacts, deals and leads into the local WireCat store: what changed since the last sync, or everything with--rescan; once a day it also finds what the portal deleted.store statussays how far each kind is. A sync stops before its deadline or--max-pages, and the next one goes on from there.deals close <ref> won|lostandleads close, to the first won or lost stage of the record's pipeline, anddeals deleteandleads delete, which ask first.deals createandupdatetake--pipeline,--stage,--amountwith--currency,--close-dateand--owner; leads take the same but--close-date. A stage checks the move key, a won or lost one the close key too.search allfinds stored deals and leads beside the other WireCat tools' messages, notes and events. Underdeals.list: denyit leaves deals out and names them inmissingand on stderr, and the same for leads; with every kind it was asked for denied, it is refused (search.all-denied).b24 --helpand a bareb24open with whatb24is for, what to try first, and where to start.
Changed — may break callers
deals,leads,pipelinesandstagesare cli-core's shared CRM commands, the same in every WireCat CRM tool. Their lists andshowread the local store, so runsyncfirst;--offlineworks for them now.- A deal or lead is named by its ref,
deal:12orlead:7, as a list prints it, not by its Bitrix24 id or title. - A deal or lead answers
ref,pipelineandpipeline_name,stage(its name),status(open,wonorlost),close_date,amountas a decimal string incurrency, bothnullwith no amount,assigneeas<portal>/user/<id>, and the portal's own fields inprovider_metadataby code.stage_id,pipeline_id,outcome,closes_on,assigned_toandperson_idsare gone from them; contacts are inpeople, the company inorganization. - Their list query takes
status,stage,pipelineandorganization; the free word, the time windows and the portal's own fields are gone from it.--allis gone from those lists. deals move <ref> <stage>takes the stage as an argument, not--stage.- Deal and lead writes have no
--body.--field <label or code>=<value>sets a custom field by the definitions the lastsyncstored: a list field takes its option's label or id, a multiple field one--fieldper value, and an empty value clears it. A code that names a standard field (stageId,categoryId,assignedById, a lead'sSTATUS_ID) is refused, and so is a--fieldwith no key before its=. - A custom field's label and a list option's label are matched as cli-core matches names, for every
--fieldand query:еfindsё, and a letter typed as two code points finds the same letter typed as one.people showandorganizations showcompare a name with the portal's matches the same way. A label that matched one field may now match two, which is refused with both. fields list <deals|leads|people|organizations>is cli-core's: it reads the definitionssyncstored, so runsyncfirst, and each field names itsuntrustedparts.api.crm.deal.update(and the other deal and leadapi.*keys) atasknow asks ondeals update,moveandclosetoo, not only onapi call.--dry-runof a deal or lead write names every key it may check: theapi.*key, and the move, close andcrm.assign-otherskeys with the option that would make each apply.syncreads the portal's field definitions of each kind, one more request per kind, so--max-pagesreaches the records later. A webhook without the rights to read them still syncs; any other failure there fails the sync.- Leads stand in one pipeline named
Leads, since Bitrix24's leads have none. deals.deleteandleads.deletedefault toask;api callthat sets a stage checksdeals.close(orleads.close) as well asdeals.move.mcpanddoctorare cli-core's shared commands:mcpaddsconfig,addandcheck, and--profilesand--permissiononserve;doctoradds--online.- A
--bodyorapi callmethod that could be read in more than one way is refused before anything is sent: a body that names one field twice in any spelling, anentityTypeIdthat is not a plain number, afieldsthat is not an object, a method with any suffix but one.json, and acrm.itemmethod withoutentityTypeId. Send each field once, under one spelling. --operation-idis gone from every write. On a live portal a create repeated with the same id made a second record, so a repeat is never safe: afteroutcome_unknown,syncandshowthe record and write it again only if it is not there. A deal or lead write given--operation-idis refused before anything is sent.
Fixed
- A
syncafter the first one works. It asked the portal for what changed with a time the portal refuses, so every kind failed and nothing new was copied. Times in a filter now go in whole seconds with an offset, which also fixesupdated_at:andcreated_at:inpeople listandorganizations list. - A write answered with a server or gateway failure (500, 502, 504) is
outcome_unknown, notprovider_unavailable: the portal may have made it. A write a cancel cuts isoutcome_unknowntoo, nevercancelled. - Stages are read page by page: a directory past REST 2's first 50 rows lost its later stages.
- An id or owner that is not a number is refused before anything is sent; it went out empty before.
--bodykeeps integers past 2^53 as typed;api call --body -reads the body from stdin.- A request that gets no answer is cut after 10 seconds, not 30, so a read can be tried again before a one-shot command's own 30 s run out.
showfor people and organizations fails when the portal's field labels fail for a reason other than missing rights, instead of showing the record without its labels.
Security
- Permission checks on
api calland onpeopleandorganizationswrites read a body exactly as it is sent, so the key they check is the key the write needs. api callreads check the same keys as the commands that read those records, as its writes already did.
0.4.0 — 06.11.2026
Added
createandupdatefor deals, leads, people and organizations take--field "<label or code>=<value>", repeated for more fields or for each value of a multiple field. The label is looked up on the portal; two fields with that label are refused with their codes. A list field takes its option's label or id.--fieldwins over--bodyfor the same field, and--title,--name,--first-nameand--last-namewin over both. Other values go to the portal as typed.--dry-rundoes not look the names up.listfor deals, leads, people and organizations filters by the portal's own fields:<code or label>:<value>, for the fields the portal can filter by; a list field takes its option's label or id. A label with spaces is writtenlead_source:weborLead\ source:web.list --json=…,provider_metadata.<code>adds that custom field to each item, asshowgives it;--json=…,provider_metadataadds every one. Only codes the portal lists are asked for.fields list <deals|leads|people|organizations>lists the portal's custom fields of that kind: code, label, type, whether it holds several values, whether a list can filter by it, and a list field's options with their ids.
Changed — may break callers
showgives a list field's value by its option's label, not its id; the id moves tooption_idbeside it. A field that holds several options gives both as arrays.api callworks from an allow list. The CRM, catalog and user reads and the four CRM types' writes are allowed as before. Any other method asks, so it needs--yeswithout a terminal, and the families that message people, publish or send data out are denied:imconnector,log.blogpost, public file links andevent.bindjoinim,imopenlinesand the rest.api shownames each method's level.
Fixed
- A
denyin a profile stops a read command again.b24handed cli-core onlyresolveSettings, so cli-core never saw b24's permission table:deals.list: denydid not stopdeals list, over MCP too, and with any b24 key in the config every--dry-runfailed with "names no command". api.crm.<type>.addandapi.crm.<type>.updatenow hold ondeals,leads,peopleandorganizationscreate,updateandmove, as the command keys already held onapi call.--dry-runnames them beside the command's own key.api call --dry-runnames every key the call checks with its level, such asapi.crm.deal.deleteatask, instead of "no key, allowed".api call crm.item.add|update|deletewithentityTypeId1 to 4 checks the lead, deal, contact or company command key, ascrm.deal.updatealready did:deals.delete: denynow stopscrm.item.deletefor a deal.- Every method whose name ends in
deleteasks, not only the listed ones. A method name in capitals or with a.jsonsuffix is read as its plain name, so it no longer gets round a key. - A write that sets a stage checks
deals.moveorleads.move:updateandcreatewithstageIdin--body, over MCP too, andapi callwithSTAGE_IDor a lead'sSTATUS_ID.ASSIGNED_BY_IDcheckscrm.assign-otherslikeassignedById. - A write is sent once. A REST 3.0 write that got no answer was sent up to three more times with the same
Idempotency-Key, which is not yet checked on a live portal; it is nowoutcome_unknownat once, and its message names--operation-idonce. pipeline_id:0, the default pipeline, is accepted bydeals listandstages list.- The allow list keeps the denied families' reads refused, as before it:
crm.activity.list,im.*,tasks.*and the rest aredeny, andapi showsays so. - The MCP server applies b24's permission table (WC-PERM-23): a command the profile denies is listed by tools search as unavailable, not offered as available.
0.3.0 — 06.11.2026
Changed — may break callers
showfor deals, leads, people and organizations gives each custom field its title on the portal:provider_metadatamaps the field's code to{ "label", "value" }.labelisnullwhen the portal has no title for it or does not answer the request for titles;showstill answers then.
0.2.0 — 06.11.2026
Fixed
- Commands that call the portal now run inside cli-core's run: each answer's
meta.run_idnames a runruns showfinds, with the portal requests in it,meta.duration_msmeasures the command, and--timeoutstops a stuck command. An MCP agent's permission overrides now reach these commands too. showfor deals, leads, people and organizations no longer lists the portal's custom fields that hold no value:provider_metadataleaves outnull,""and[], and is left out when nothing is left.
0.1.0 — 06.11.2026
Fixed
- An error reason that a newer cli-core also defines no longer makes every failing command crash instead of printing its error.
Added
- Started from
@wirecat/cli-template0.14.0. - The client for Bitrix24's REST API over an incoming webhook: REST 3.0 for deals, leads, contacts and companies,
REST 2 for the rest; the portal's rate limits paced and waited out; a write repeated only with an
Idempotency-Key; the webhook's secret kept out of every error and run record. session start | status | endper profile andsession list: the webhook address read hidden or from stdin, checked against the portal, kept in the keyring underbitrix24-cli;session status --checksays whose rights the webhook carries.listandshowfordeals,leads,people(contacts),organizations(companies),pipelines,stages(with each stage's outcome),productsandusers, live from the portal. A list takes a query that becomes the portal's own filter; a free word in it matches part of a title or name. A name two records share is refused with both as candidates.createandupdatefordeals,leads,peopleandorganizations, andmovefor deals and leads: each write answers itsoperation_id, which Bitrix24 receives as theIdempotency-Key, so--operation-idrepeats a write safely;--dry-runpreviews; assigning a record to another employee is denied until allowed (crm.assign-others).api list,api show <method>andapi call <method> --body <json>: any Bitrix24 method, on the REST version it lives on, checkingapi.<method>and the command key it does the same as; messaging, automation, tasks andbatchwrites are denied until allowed.