For AI agents: markdown of this page — /docs-content-en/changelog/2026-07-14.md documentation index — /llms.txt
API changes: July 14, 2026
NEW-0714-1: OpenAPI spec: 3.1 validity, field semantics, and a scope slice
Before
The machine-readable spec GET /v1/openapi.json carried the 3.0 nullable keyword (invalid under 3.1), left {entityTypeId} undeclared on batch/aggregate/fields/products, shipped no property descriptions/allowed-values/examples, described only vibe_app_ keys, and served one monolith.
After
The spec is valid under OpenAPI 3.1: nullable fields use the union type ["<type>","null"] and every path parameter is declared. Properties now carry title/description, allowed values (x-enumValues plus an in-description decode), and examples. Security schemes name all three key families (vibe_api_/vibe_app_/vibe_live_), every operation carries x-required-scope, and the root exposes an x-scopes catalog. Root tags/externalDocs, a webhooks block for bot events, and a multifield anyOf were added. GET /v1/openapi.json?scope=<scope> (e.g. ?scope=crm) returns a single-scope slice so it fits an agent context window. Pointers to the spec were added to /v1/me and /v1/guide. A full port of per-operation curl examples is a separate follow-up.
FIX-0714-2: Generic 5xx errors on AI endpoints now use the OpenAI-compatible envelope
AI endpoints (/v1/ai/*, /v1/models, /v1/chat/*, /v1/audio/*) are documented with an OpenAI-compatible error format. Pool-exhaustion errors already followed this, but a generic (unexpected) 5xx on these endpoints returned the plain V1 envelope instead.
Before
A generic 5xx on an AI endpoint: {"success": false, "error": {"code": "...", "message": "..."}} — not the shape OpenAI SDK clients expect for these paths.
After
The same case now returns {"error": {"message": "...", "type": "...", "code": "..."}} — one envelope for every error on AI endpoints, including generic 5xx.
Impact on integrators
Clients on the OpenAI SDK that already read error.type/error.code (the standard path for these endpoints) see no change. Code that expected top-level success/error.code specifically on a generic 5xx from an AI endpoint should switch to error.type/error.code.
FIX-0714-3: `versions` in the app source-history list is capped at 500 entries
GET /v1/apps/:id/sources returned the entire version history with no limit — for apps with very long histories this was an unbounded read.
Before
data.versions — the entire version list with no size limit; data.totalVersions always equaled data.versions.length.
After
data.versions holds at most the 500 most recent versions (by savedAt, descending). data.totalVersions and data.totalSizeBytes are still computed over the full set — aggregate accuracy doesn't depend on the cap.
Impact on integrators
For apps with up to 500 versions of history, behavior is unchanged. For apps with more than 500 versions, data.versions.length may now be smaller than data.totalVersions — code that relied on them being equal should use data.totalVersions/data.totalSizeBytes for aggregates and not treat data.versions as the complete list.
FIX-0714-4: Telephony: `userId`/`duration` are now genuinely validated, not just checked for truthy
POST /v1/calls/register, /v1/calls/:callId/show, /v1/calls/:callId/hide, /v1/calls/:callId/finish accepted userId (and duration on finish) without checking its type or shape — any truthy value (for example the string "abc" or an object) was forwarded to Bitrix24 and failed there with an opaque upstream error.
Before
{"userId": "abc"} (or any other truthy value that wasn't a positive integer) passed validation and was sent to Bitrix24; the Required: userId (number) error only appeared for a fully empty/falsy value.
After
userId is accepted as a positive integer or a numeric string ("42"), otherwise a clean 400 MISSING_PARAMS with the refined text Required: userId (positive integer) (plus phoneNumber for register). duration on finish follows the same rule — a non-negative number or a numeric string, otherwise 400.
Impact on integrators
Correct calls (userId as a number or numeric string) are unchanged. Calls that previously "got through" with an invalid userId/duration (not a number, not a numeric string) now get an explicit 400 instead of an opaque Bitrix24-side error.
FIX-0714-5: Creating a comment on a missing task — an explicit error instead of a false success
POST /v1/tasks/:taskId/comments and its batch counterpart POST /v1/tasks/:taskId/comments/batch (action: create) call Bitrix24 to create a comment on the legacy task card. If the task doesn't exist or isn't accessible to the key, Bitrix24 doesn't create the comment and returns no identifier.
Before
Both endpoints responded with success and an empty identifier — the single call as 201 {"success": true, "data": {"id": null}}, the batch item as {"success": true, "id": null}. No comment was created, but the integrator couldn't distinguish this from the normal case.
After
The single call now returns 404 TASK_NOT_FOUND. The batch variant marks the corresponding item as {"success": false, "error": "TASK_NOT_FOUND"} without cancelling the rest of the batch. A separate, unrelated case is unchanged: on the new task card the comment goes through chat, and if the system couldn't recover its id via a follow-up search, the response is still success: true with id: null — the comment was genuinely created in that case.
Impact on integrators
Code that checks data.id / data[i].id for null as an error signal keeps working unchanged and now gets a more precise error code. Code that relied on a silent success with id: null for an inaccessible task should handle 404 / TASK_NOT_FOUND explicitly.
FIX-0714-6: Rate limit for `/v1/search`, `/v1/research`, `/v1/batch` is now per-account
Before
The limits (/v1/search 60/min, /v1/research 20/min, /v1/batch 30/min) were effectively keyed by source IP, not by account. An account using multiple API keys (or several accounts behind one shared egress IP) could exceed the documented cap, and the limit was bypassable by IP rotation.
After
The limit is now keyed per Bitrix24 account: all API keys of one account share a single bucket (60 / 20 / 30 requests per minute respectively). The documented per-tenant cap is now enforced correctly and cannot be bypassed by using more keys or rotating IPs.
Integrator impact
If your account spread /v1/search / /v1/research / /v1/batch traffic across several API keys, the effective ceiling is now the single account limit, not the sum across keys. On exceeding it you get 429 with a Retry-After header (as before).
FIX-0714-7: Calendar: working section batch-delete, clean update errors, no leaked sync fields
Before
POST /v1/calendar-sections/batchwithaction: "delete"had no channel for thetype/ownerIdthatcalendar.section.deletemandates — every item failed on both platforms.PATCH /v1/calendar-sections/{id}withouttype/ownerId/nameforwarded to Bitrix24 and returned a raw422leaking the internal method name.GET /v1/calendar-sectionsreturned the undocumented raw Bitrix24 fieldsGAPI_CALENDAR_ID,CAL_DAV_CON,SYNC_TOKEN,PAGE_TOKEN,EXTERNAL_TYPE(three of them sync tokens).GET /v1/calendar-eventsandGET /v1/calendar-events/{id}leaked the internalattendeesEntityListfield — the schema tried to strip it but no-op'd on a key-casing mismatch.
After
- Section batch-delete reads
type/ownerIdfrom the body alongsideidsand threads them into every delete command. A missing anchor is a clean400 MISSING_REQUIRED_PARAMSbefore the Bitrix24 call. - Section partial-update requires the
type/ownerId/nameanchors (sections have no get-by-id to backfill) — a clean400, no raw422, no leaked method name. - Both calendar read paths strip the listed internal/sync fields from the response.
Affected endpoints:
POST /v1/calendar-sections/batchPATCH /v1/calendar-sections/{id}GET /v1/calendar-sectionsGET /v1/calendar-events
FIX-0714-8: PATCH catalog-product-properties works again (was an inescapable catch-22)
Before
Updating a product property was impossible under any body: PATCH without iblockId → 422 ("Required fields: iblockId" — B24 mandates it on every update), and PATCH with iblockId → 400 READONLY_FIELD (create-only field). The entire UPDATE verb was dead — no field could be changed after create.
After
iblockId is now carried over automatically from the existing record (a pre-fetch, like catalog-sections), so PATCH {name:"…"} reaches B24 with the required iblockId and returns 200. You still don't send iblockId in the body (and it is still rejected as read-only if you do) — the service supplies it.
Integrator impact
If your product-property PATCH always failed 422/400, now send only the fields you're changing (PATCH {name:"…"}); iblockId is not required.
FIX-0714-9: entityTypeId validation: junk forms → 400 instead of silent truncation
Before
Five surfaces parsed entityTypeId (the smart-process / dynamic-entity TYPE selector) leniently — with unanchored parseInt or coercing Number() — and a junk form silently turned into a DIFFERENT (within-account) entity type:
- The path
entityTypeId(/v1/items/:entityTypeId/...,/v1/categories/:entityTypeId/...— CRUD and/aggregate):GET /v1/items/1058abctruncated to1058,1e3→1,1.5→1. - Global
POST /v1/batch:params.entityTypeIdviaNumber()accepted fractions (1.5), hex ('0x10'→ 16), overflow toInfinity, plus the array form[1058]→ 1058. /v1/items/:entityTypeId/userfields/*: its own parser —2abcresolved the userfields of type2.POST /v1/smart-processes/batch:ids: ['1030abc']truncated to1030— delete/update silently ran against a REAL, DIFFERENT type; a fractional number (1030.5) passed too.POST /v1/triggers/fire(entityType="item"):entityTypeId: '1038abc'→ the automation trigger fired against type1038.
After
All five surfaces require the canonical positive-integer form (/^[1-9]\d*$/ for strings, Number.isInteger for numbers): any other form → 400 with each surface's existing error code (INVALID_DYNAMIC_PARAM / INVALID_ENTITY_TYPE_ID / BATCH_ITEM_VALIDATION / MISSING_PARAMS) BEFORE any Bitrix24 call. Aggregate now shares the CRUD routes' validator instead of an inline copy.
Integrator impact
Forms that previously coerced to a correct value and were served — 007 → 7, %20-spaces, +2 → 2, the array form [1058] in batch — are now rejected with 400 as well: the value must be a canonical integer with no prefixes, suffixes or leading zeros. A boolean in batch was rejected before too (it coerced into a reserved type); only its error code changes — now INVALID_DYNAMIC_PARAM. Correct calls are unchanged.
FIX-0714-10: Reopening feedback clears the resolution fields
Before
PATCH /v1/feedback/:id (and the admin endpoint PATCH /api/platform/feedback/:id, which the admin UI reopens through) moving a ticket back to an active status (NEW, REVIEWING, AWAITING_USER, NEEDS_REVIEW) from RESOLVED/WITHDRAWN did not reset resolvedAt, resolvedBy, or resolution — they lingered from the prior close, so a reopened ticket looked both active and resolved.
After
Moving a ticket out of RESOLVED/WITHDRAWN into an active status clears resolvedAt, resolvedBy, and resolution. RESOLVED/WITHDRAWN still set the resolution stamp; ARCHIVED leaves the fields untouched (archiving preserves the resolution history). A plain transition between active statuses (e.g. AWAITING_USER → REVIEWING) leaves the fields alone — on active tickets resolution mirrors the last team comment. An explicit resolution in the same request still wins over the clear.
Integrator impact
If you read resolvedAt/resolution on a reopened ticket and got the prior close's values, they are now null for an active ticket.
FIX-0714-11: `INVALID_JSON_BODY` no longer quotes the engine parser text
Before
Six route groups (/api/billing/*, /v1/apps*, /v1/bots*, /v1/keys*, /v1/note*, /v1/infra/servers/* deploy/exec/upload) answered malformed JSON with 400 and a message like Invalid JSON: Unexpected token } in JSON at position 41 — raw V8 engine text (a runtime fingerprint and an implementation detail). The deploy/exec/upload group set no error code at all.
After
All six now return the single static message Request body is not valid JSON. — matching /v1/<entities> (the same class is closed there by a separate fix). On V1 surfaces the code is INVALID_JSON_BODY (deploy/exec/upload now sets it too); the 400 status is unchanged.
Integrator impact
If your code parsed the message text (e.g., extracted the error position), rely on the INVALID_JSON_BODY code instead; the position is no longer reported.
FIX-0714-12: Quote GET response returns amount, currency, and dates again (were null)
Before
Reading a quote (GET/list/search /v1/quotes) returned null for amount, currency, beginDate, closeDate — the values leaked only under the raw Bitrix24 keys (opportunity, currencyId, begindate, closedate). Writes worked, but the READ projection dropped every aliased field: a quote's total and currency were 100% invisible via the documented API.
After
The READ branch now reverse-maps the declared aliases (mirroring the write mapping): opportunity → amount, currencyId → currency, begindate → beginDate, closedate → closeDate, with type coercion. The raw Bitrix24 keys no longer appear in the response.
Integrator impact
If you read a quote's amount/currency and got null, they are now populated. Code that worked around it by reading the raw opportunity/currencyId from the response will no longer find them there — switch to the documented amount/currency.
FIX-0714-13: Write-path validation: phantom checklist → 404, garbage types and junk `:id` → 400
Before
POST /v1/tasks/:taskId/checklistagainst a nonexistent task returned201with a plausibleid, yet nothing was created (the item was never GETtable).POST /v1/warehousesaccepted non-stringtitle/address(numbers, objects) and forwarded them to Bitrix24 with unpredictable results;POST /v1/doc-templateslikewise passed non-stringname/regionand non-numericnumeratorIdthrough.- A non-numeric
:idon entities with a typed numeric id (GET/PATCH/DELETE /v1/quotes/abc,/v1/deals/1.5,/v1/leads/1e3) went to Bitrix24 verbatim — returning an opaque B24 error instead of a clear code. On smart-processes12abcwasparseInt-truncated to12and hit the WRONG type.
After
- Checklist: the parent task is verified before the item is created; a missing (or invisible-to-the-key) task →
404 TASK_NOT_FOUND. - Warehouses and document templates: a wrong-typed value →
400 INVALID_PARAMSwith no Bitrix24 call (a numeric string innumeratorIdis still accepted). - Entities with an explicitly typed numeric id: a non-canonical-integer
:id→400 INVALID_PARAMSbefore any Bitrix24 call (id0— the main deal pipeline ofcategories— stays valid). Smart-processes keepINVALID_ENTITY_TYPE_IDand now reject12abcon GET/PATCH/DELETE instead of truncating it to12. Entities whose id type is not declared in the schema keep their prior pass-through behavior.
Affected endpoints: POST /v1/tasks/:taskId/checklist, POST /v1/warehouses, POST /v1/doc-templates + GET/PATCH/DELETE on entities with a typed numeric id.
Integrator impact
If your code relied on the phantom checklist 201 or sent garbage-typed values hoping for the best, you will now get an explicit 4xx with a code. Correct calls are unchanged.
FIX-0714-14: Five silent false-success / hint defects: an honest response instead of a fake success
Before
PATCH /v1/userfields/{entity}/{id}withlabelwas a silent no-op on update:200, but the field label never changed (Bitrix24crm.*.userfield.updateignoresLABEL).POST /v1/humanresources/nodes/{id}withparentIdfaked a successful reparent:namewas applied,parentIdwas silently ignored, and the response echoed the stale parent.POST /v1/chats/messages/bulkdid not resolve thedialogId: "me"alias inside the bulk loop (single routes do) → messages went to the wrong dialog.POST /v1/bots/{botId}/chats/{dialogId}/users— theUSERS_NOT_ADDEDsafety-net was dead code (it never matched the v2 method's response shape) → a failed add passed as success.crm.item.listerrors received an irrelevant "Maximum 50 records…" hint even when the cause was something else (e.g. "entity type does not exist").
After
labelon update fans out to the realEDIT_FORM_LABEL/LIST_COLUMN_LABEL/LIST_FILTER_LABELparams — the label actually changes.parentIdandtypeare now create-only: onPATCHthey are rejected with400(reparent viaPOST /v1/humanresources/nodes/{id}/move) instead of faking success.- Bulk message reads resolve
dialogId: "me"per item, like the single routes. - The bot-chat add safety-net fires again: users that were not added come back in
warning.USERS_NOT_ADDED. - Known-limitation hints are gated on the error message's relevance, not the method name alone.
Affected endpoints:
PATCH /v1/userfields/{entity}/{id}PATCH /v1/humanresources/nodes/{id}POST /v1/chats/messages/bulkPOST /v1/bots/{botId}/chats/{dialogId}/users
FIX-0714-15: GET /v1/task-time now honestly returns more than 50 rows when limit>50
Before
GET /v1/task-time with a limit above 50 returned only 50 rows even though meta.limit echoed the requested value and meta.hasMore could mislead. A client paginating with a step above 50 silently lost rows.
After
The request now returns up to limit rows (max 500), collected page-by-page on the backend; meta.total and meta.hasMore match the window actually returned. With a limit above 50, offset now points at the correct position instead of shifting onto the first pages.
NEW-0714-16: GET /v1/companies/fields and catalog-prices system fields now carry label and description
GET /v1/companies/fields now returns human-readable label and description for every company field. GET /v1/catalog-prices/fields adds the same metadata to the system fields extraId, priceScale and timestampX. Labels come in English. A field's meaning can now be read programmatically from the response instead of cross-referencing static documentation.
FIX-0714-17: /stop and /reboot distinguish a missing server from a wrong status
Before
POST /v1/infra/servers/:id/stop and POST /v1/infra/servers/:id/reboot on a server that was not in running status (e.g. sleeping) returned a flat 404 NOT_FOUND reading "Running server not found" — from which you could not tell the server still existed, so an agent concluded it had been deleted.
After
Both routes now behave like /start and /wake: 404 SERVER_NOT_FOUND only when no server with this id exists; 422 SERVER_WRONG_STATE when the server exists but is not in running status. error.currentState carries the current state and error.availableActions lists the actions available now (wake/start/repair/delete).
FIX-0714-18: clearer /fields error for task comments and task time entries
Before
GET /v1/tasks/:taskId/comments/fields returned a confusing 400 INVALID_PARAMS reading "taskId and id must be positive integers" (you asked about fields, the answer was about an id), and GET /v1/tasks/:taskId/time/fields leaked a raw Bitrix24 error exposing an internal PHP method and an HTML tag.
After
Both entities recognise the fields segment and return a clear 400 WRONG_PATH: they have no /fields method (the field schema is documented in /v1/guide), and the message lists the valid routes. A non-numeric or fractional id on the by-id routes is now rejected as 400 INVALID_PARAMS before the Bitrix24 call — no internal error leaks out.
FIX-0714-19: `/v1/ai/credentials*` rate limits are now per-account; `CREDENTIAL_NOT_FOUND` carries a hint
Before
- The BYOK route limits (
POST /v1/ai/credentials,/:id/test,/:id/fetch-models— 10/min;/:id/modelsadd/delete — 30/min) were effectively keyed by source IP: credential probing was bypassable by IP rotation, and tenants behind one shared egress IP shared a bucket.PATCH /:id(which verifies the key upstream whencredentialsis sent — the same oracle as/:id/test) had no limit at all. - The
404 CREDENTIAL_NOT_FOUNDfrom/v1/searchand/v1/researchcarried only the provider slug — no pointer to how to configure a key.
After
- The limit is keyed per Bitrix24 account: all API keys of one account share a single bucket; IP rotation and key count no longer affect the cap. On exceeding it you get
429withRetry-After. AdditionallyPATCH /:id(which verifies the key upstream, like/:id/test) previously had NO limit — it is now also 10/min per account. - The
CREDENTIAL_NOT_FOUNDresponse now includes ahintfield with the exact recipe:POST /v1/search/credentials {provider, apiKey}; provider list —GET /v1/search/providers.
FIX-0714-20: PATCH for bizproc templates, robots and activities via /v1 now applies changes
Before
PATCH /v1/bizproc-templates/:id, /v1/bizproc-robots/:code and /v1/bizproc-activities/:code with metadata fields (name, description, autoExecute) returned 422 BITRIX_ERROR "No fields to update." — updates were impossible (the same in batch requests). An autoExecute value sent as a number was additionally rejected as Incorrect field AUTO_EXECUTE!.
After
Fields are applied correctly (including autoExecute sent as a number); the endpoint confirms success and returns the id of the updated entity. Both single PATCH and batch requests work. Creation (POST) is unchanged.
FIX-0714-21: Transcription: wallet check before the recognition call
Before
POST /v1/audio/transcriptions (and /v1/ai/audio/transcriptions) did not check the wallet before calling the upstream: a PREPAY account past its overdraft (but not yet frozen by the background sweep) still triggered recognition and slid the balance deeper negative. Chat and embeddings already rejected such calls up front; a fully frozen account was always blocked globally (ACCOUNT_FROZEN).
After
Same as chat and embeddings: the wallet check runs before the Whisper call. An exceeded overdraft → 402 insufficient_balance, recognition never starts. BYOK keys (USER scope) are free — no check, no behavior change.
FIX-0714-22: app deletion is no longer blocked by a galaxy host on its key
Before
DELETE /v1/apps/:id returned 409 APP_HAS_ACTIVE_SERVERS when a galaxy host (shared account infrastructure) happened to sit on the application's key. The app could not be deleted, and the response gave no explanation.
After
A galaxy host is excluded from the blocking-servers check: it is managed at the account level, not the key level, so it must not block app deletion. Standalone application servers (including application containers) still block deletion with 409 APP_HAS_ACTIVE_SERVERS — rebind them to another key first.
FIX-0714-23: OpenAPI: per-entity batch endpoint body is now documented correctly (action + items/ids/calls)
Before
The spec (GET /v1/openapi.json) documented every per-entity batch body as {create:[], update:[], delete:[]}. The runtime (shared batch handler) requires {action, items|ids|calls} and returns 400 INVALID_BATCH_ACTION for the documented shape. A client generated from the spec (codegen / AI agent) got 100% batch-write failure across all ~45 per-entity batch endpoints. The feature itself works — only the spec was wrong (the global POST /v1/batch, /v1/tasks/{taskId}/comments/batch, and /v1/guide already documented the correct shape).
After
The spec generator emits the correct shape: a single action (create/update/delete/list/get/fields); create/update send items, delete sends ids, reads send calls. Matches the runtime and the global /v1/batch.
Integrator impact
If you generated a client from openapi.json and batch-write failed with INVALID_BATCH_ACTION, regenerate it: the body is now {action:"create", items:[…]} instead of {create:[…]}. Hand-written clients that already sent {action,…} are unaffected.
FIX-0714-24: Currencies: fullName, format, and decimals now persist on a flat write
Before
POST/PATCH /v1/currencies with flat fullName, formatString, decimals, decPoint, thousandsSep returned success but silently dropped the values — Bitrix24 stores them per-language (LANG) and the API sent them flat. The documented workaround "send a raw LANG" did not work either: LANG is a read-only field, so the request was rejected.
After
The API packs the flat localizable fields into your language's localization (the API key's language) before the Bitrix24 call, so a flat write persists and reads back (POST {fullName:"…",decimals:3} → GET returns them). This works on every write path: single-route, POST /v1/currencies/batch, and the global POST /v1/batch. A raw LANG in the body is still rejected as read-only. Setting different values for several languages at once via the API is not yet supported. The write language is the API key's locale (ru or en) and may differ from the currency's display language in the Bitrix24 account — on accounts with another locale (de/pl/ua…) the edit lands under en.
Integrator impact
If you worked around the bug with a raw LANG (and hit 400 READONLY_FIELD), drop it and send the flat fields. Flat requests that already worked now also persist the values.
FIX-0714-25: Aggregate enforces required filters; infra validation no longer leaks raw Zod
Before
POST /v1/<entity>/aggregateon an entity that mandates a filter (e.g.catalog-productsneedsiblockId) let an empty request reach Bitrix24 and returned a raw422, while GET-list/search return a clean400on the same condition.POST /v1/infra/servers/:id/{deploy,exec,upload,logs}put a multi-line JSON-serialized Zod issue array intoerror.messageon a body-validation error (a raw validator fingerprint).
After
- Aggregate checks required filters/params before the Bitrix24 call: a missing required filter →
400 MISSING_REQUIRED_FILTER(e.g.catalog-products→iblockId); a missing required list-param →400 MISSING_REQUIRED_PARAMS(e.g.calendar-events→type,ownerId;humanresources-nodes→type). Like list/search. - Infra validation formats the error compactly (
field: message; …), matching the siblinginfra.ts. The code (VALIDATION_ERROR) and400status are unchanged.
Integrator impact
If you caught a raw 422 from a filter-less aggregate, you now get 400 MISSING_REQUIRED_FILTER. If you parsed infra error.message as JSON, it is now a flat field: message string.
FIX-0714-26: Batch: per-entity batch works for items, and folder creation via batch
Before
POST /v1/items/{entityTypeId}/batchreturned404— the per-entity batch route for dynamic-param entities (items,categories) was mounted at the param-less path (/v1/items/batch), so the documented path didn't resolve and theentityTypeIdnever reached the Bitrix24 command. The globalPOST /v1/batchfallback worked.POST /v1/folders/batchwithaction: "create"failed every item withERROR_ARGUMENT: batch-create sentfields[...], butdisk.folder.addsubfolderexpects the parent folder as a top-levelidand the rest underdata[...].
After
- The per-entity batch route for
items/categoriesis mounted with the:{entityTypeId}segment and threads the validatedentityTypeId(positive integer; dedicated-API ids likedeals=2are rejected with a pointer, same as the single routes) into every command — for all actions:create/update/deleteand the read actionslist/get/fields. - Folder batch-create mirrors the single-route shape:
id=<parent>&data[...]. A missingparentIdis a clean per-item400before the Bitrix24 call.
Affected endpoints:
NEW-0714-27: recover a stuck server exec channel
A new endpoint POST /v1/infra/servers/:id/unstick force-frees a Black Hole server's stuck command channel when a deploy or exec keeps returning EXEC_BUSY ("Another command is running") even after DELETE /v1/infra/servers/:id/lock. It releases the platform-side lock and bounces the agent tunnel — on reconnect the agent finishes the stuck command and frees its mutex. The server is not rebooted.
If a legitimate operation (a deploy, exec, or harden) is still running on the server when you call it, the endpoint returns 409 OPERATION_IN_PROGRESS by default and leaves it alone — only a genuinely stuck channel should be unstuck. Retry with ?force=true if you are certain the command channel is hung.
Response: { success: true, data: { backendLockReleased, agentBounced, reconnected } }. Error codes: 404 SERVER_NOT_FOUND, 409 CONFLICT (a recovery is already running), 409 OPERATION_IN_PROGRESS (an operation is running on the server — retry with ?force=true), 409 GALAXY_UNSTICK_UNSUPPORTED (not supported for galaxy hosts or galaxy apps), 502 GATEWAY_ERROR. The /exec error (EXEC_BUSY) and a /deploy failure (code DEPLOY_FAILED, message "Another command is running") now also carry a hint pointing at this endpoint.
NEW-0714-28: server description in `PATCH /v1/infra/servers/:id`
Before
PATCH /v1/infra/servers/:id accepted only displayName. There was no description field in the contract, and GET responses did not expose one.
After
PATCH /v1/infra/servers/:id accepts an optional description field (string, up to 500 characters; an empty string or null clears the description; omitting the field leaves the current value unchanged). The value is synced to the application's catalog card. The description field is now returned in GET /v1/infra/servers, GET /v1/infra/servers/:id, and in the PATCH response. Existing requests without description keep working unchanged.
FIX-0714-29: galaxy app deploy returns an honest error instead of a false success when the connection drops mid-build
Before
If the connection to the host dropped during a galaxy app build (common under heavy-build load), POST /v1/infra/servers/:id/deploy could return 200 with status running and a [recovered] note in buildLog, even though the new version never built or started — the previous container kept running. A retry hit the same drop and again reported a false success.
After
A deploy is treated as recovered only if it completed fully: the container running under the app's name is the one this attempt started, and the deploy ran to the end. If the connection dropped during the build — or at any point before the deploy completed — and the new version did not come up fully, the endpoint returns a retryable 502 with code GALAXY_HOST_UNREACHABLE and a hint to re-send the same deploy without deleting the server — instead of a false 200. Only if the deploy completed fully and just the final response was lost does recovery to 200 work as before.
BC-0714-30: renaming a catalog app reaches Bitrix24, publish loses menuTitle
Old format supported until: 14.01.2027
Before
PATCH /v1/apps/:id with a title field on an app that is in the catalog returned 200 but changed nothing the user could see: the catalog card and the placement bindings on the Bitrix24 account (the left-menu item, CRM tabs) kept the old name. There was never a failure — the call always succeeded.
For POST /v1/apps/:id/publish the body was not validated, and the placement title was set by a separate menuTitle field.
After
For an app in the catalog, title is a single operation on the display name: the name is synchronized into the catalog card (catalogTitle is written together with title) and re-bound into the placements on the Bitrix24 account. A call that always returned 200 can therefore now fail honestly:
400 NO_USER_TOKEN— the app is not authorised on the Bitrix24 account, so there is nothing to re-bind the placements with;400 TITLE_TOO_LONG_FOR_CATALOG— a catalog app name is capped at 100 characters, whiletitleallows 255;502 BITRIX_PARTIAL_REBIND— Bitrix24 rejected the binding. The name is not written in that case, and repeating the same request repairs the state.
The body of POST /v1/apps/:id/publish is now validated, and the menuTitle parameter is removed: the placement title is always the app's display name. An empty body still works — publication takes the values from the app record.
What integrators should do
- Drop
menuTitlefrom the publish body: the field is ignored, the menu item name comes fromtitleandcatalogTitle. - Keep a catalog app name within 100 characters.
- Handle the rejections on a rename: on
NO_USER_TOKENauthorise the app on the Bitrix24 account, onBITRIX_PARTIAL_REBINDrepeat the request. - Note the side effect: renaming through
titlenow also writescatalogTitle— for an app in the catalog the two fields are kept in sync.
NEW-0714-31: GET /v1/tasks/:taskId/comments/fields — task-comment field schema
Task comments gained a /fields method like every other entity: GET /v1/tasks/:taskId/comments/fields returns a static 5-field schema (id, taskId, authorId, message, createdAt) with type, read-only flag, and segment-localized label and description. The method makes no Bitrix24 call. This path previously returned 400 WRONG_PATH — the field set was only available from the static docs.
FIX-0714-32: per-entity batch with action list now applies filter
Before
POST /v1/{entity}/batch with action: "list" ignored filter: field names were not mapped to their Bitrix24 names (e.g. a leads statusId was not turned into stageId), and operators $gt / $contains / $in and others had no effect. The call returned 200 with the whole table — a silent failure with wrong data. The global POST /v1/batch and POST /v1/{entity}/search filtered correctly.
After
Per-entity batch runs filter through the same translator search and the global /v1/batch use. Field aliases and operators ($gt, $gte, $lt, $lte, $ne, $contains, $in, $nin, prefix >=, <=, !, etc.) are applied. An invalid filter (an unknown field on an entity with a complete schema, an unsupported operator, the @ / !@ prefixes, or $or / $and logic tokens) now returns 400 naming the call index instead of silently returning the whole set — matching the single endpoints.
FIX-0714-33: auto-pagination no longer duplicates records across page boundaries
Before
For Bitrix24 list methods without sort support (e.g. the storage-object list) a record on a page boundary could shift between the fetches of adjacent pages and land in both — with limit > 50 the response carried a duplicate that occupied a slot, and the client processed the same record twice.
After
After all pages are stitched, the result is deduplicated by id (the first occurrence is kept). For stably-sorted methods nothing changes (no duplicates — a no-op).
FIX-0714-34: requisite-preset field list no longer comes back empty; create no longer returns a foreign record
Before
GET /v1/requisite-presets/:presetId/fields could return 200 with an empty data: [] even when the preset had fields: the Bitrix24 method returns result sometimes as an array [{…}] and sometimes as an object-map {"0":{…},"1":{…}}, and the handler accepted only the array form. On create (POST …/fields) the echoed record could be a DIFFERENT existing field — the created row was read back by the id from the add response, and on some accounts a read by that id returned another field.
After
The list normalizes both Bitrix24 response shapes (array and object-map) — fields are no longer dropped. The created record is echoed only when its fieldName matches the one that was created; on any mismatch the response carries { id } (the row object is not substituted), so a client never receives a foreign record.
Integrator impact
A preset field's id is a Bitrix24 positional identifier: it can change after write operations on the preset and, on some accounts, is not a stable key. Do not cache id across preset mutations — re-fetch the field list before a get/update/delete on a specific field.
FIX-0714-35: placement bind for earlier-created apps is no longer rejected over the handler
Before
POST /v1/placements/bind could return 400 with code PLATFORM_HANDLER_UNRESOLVABLE for an app created before the switch to the single platform handler (such an app kept its own technical address as the handler). The bind was rejected even when the platform handler /v1/bitrix-handler was available — the app could not be re-published through the API.
After
The bind succeeds: the placement handler is registered on the platform /v1/bitrix-handler, and the response carries handlerRewritten: true plus requestedHandler with the original value. PLATFORM_HANDLER_UNRESOLVABLE is now returned only when the platform handler is genuinely unavailable. POST /v1/placements/unbind removes such a placement by the same address.