For AI agents: markdown of this page — /docs-content-en/changelog/2026-07-14.md documentation index — /llms.txt

API changes: July 14, 2026

← Changelog · July 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/batch with action: "delete" had no channel for the type/ownerId that calendar.section.delete mandates — every item failed on both platforms.
  • PATCH /v1/calendar-sections/{id} without type/ownerId/name forwarded to Bitrix24 and returned a raw 422 leaking the internal method name.
  • GET /v1/calendar-sections returned the undocumented raw Bitrix24 fields GAPI_CALENDAR_ID, CAL_DAV_CON, SYNC_TOKEN, PAGE_TOKEN, EXTERNAL_TYPE (three of them sync tokens).
  • GET /v1/calendar-events and GET /v1/calendar-events/{id} leaked the internal attendeesEntityList field — the schema tried to strip it but no-op'd on a key-casing mismatch.

After

  • Section batch-delete reads type/ownerId from the body alongside ids and threads them into every delete command. A missing anchor is a clean 400 MISSING_REQUIRED_PARAMS before the Bitrix24 call.
  • Section partial-update requires the type/ownerId/name anchors (sections have no get-by-id to backfill) — a clean 400, no raw 422, no leaked method name.
  • Both calendar read paths strip the listed internal/sync fields from the response.

Affected endpoints:

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/1058abc truncated to 1058, 1e3 → 1, 1.5 → 1.
  • Global POST /v1/batch: params.entityTypeId via Number() accepted fractions (1.5), hex ('0x10' → 16), overflow to Infinity, plus the array form [1058] → 1058.
  • /v1/items/:entityTypeId/userfields/*: its own parser — 2abc resolved the userfields of type 2.
  • POST /v1/smart-processes/batch: ids: ['1030abc'] truncated to 1030 — 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 type 1038.

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/checklist against a nonexistent task returned 201 with a plausible id, yet nothing was created (the item was never GETtable).
  • POST /v1/warehouses accepted non-string title/address (numbers, objects) and forwarded them to Bitrix24 with unpredictable results; POST /v1/doc-templates likewise passed non-string name/region and non-numeric numeratorId through.
  • A non-numeric :id on 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-processes 12abc was parseInt-truncated to 12 and 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_PARAMS with no Bitrix24 call (a numeric string in numeratorId is still accepted).
  • Entities with an explicitly typed numeric id: a non-canonical-integer :id → 400 INVALID_PARAMS before any Bitrix24 call (id 0 — the main deal pipeline of categories — stays valid). Smart-processes keep INVALID_ENTITY_TYPE_ID and now reject 12abc on GET/PATCH/DELETE instead of truncating it to 12. 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} with label was a silent no-op on update: 200, but the field label never changed (Bitrix24 crm.*.userfield.update ignores LABEL).
  • POST /v1/humanresources/nodes/{id} with parentId faked a successful reparent: name was applied, parentId was silently ignored, and the response echoed the stale parent.
  • POST /v1/chats/messages/bulk did not resolve the dialogId: "me" alias inside the bulk loop (single routes do) → messages went to the wrong dialog.
  • POST /v1/bots/{botId}/chats/{dialogId}/users — the USERS_NOT_ADDED safety-net was dead code (it never matched the v2 method's response shape) → a failed add passed as success.
  • crm.item.list errors received an irrelevant "Maximum 50 records…" hint even when the cause was something else (e.g. "entity type does not exist").

After

  • label on update fans out to the real EDIT_FORM_LABEL/LIST_COLUMN_LABEL/LIST_FILTER_LABEL params — the label actually changes.
  • parentId and type are now create-only: on PATCH they are rejected with 400 (reparent via POST /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:

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/models add/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 when credentials is sent — the same oracle as /:id/test) had no limit at all.
  • The 404 CREDENTIAL_NOT_FOUND from /v1/search and /v1/research carried 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 429 with Retry-After. Additionally PATCH /:id (which verifies the key upstream, like /:id/test) previously had NO limit — it is now also 10/min per account.
  • The CREDENTIAL_NOT_FOUND response now includes a hint field 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>/aggregate on an entity that mandates a filter (e.g. catalog-products needs iblockId) let an empty request reach Bitrix24 and returned a raw 422, while GET-list/search return a clean 400 on the same condition.
  • POST /v1/infra/servers/:id/{deploy,exec,upload,logs} put a multi-line JSON-serialized Zod issue array into error.message on 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 sibling infra.ts. The code (VALIDATION_ERROR) and 400 status 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}/batch returned 404 — 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 the entityTypeId never reached the Bitrix24 command. The global POST /v1/batch fallback worked.
  • POST /v1/folders/batch with action: "create" failed every item with ERROR_ARGUMENT: batch-create sent fields[...], but disk.folder.addsubfolder expects the parent folder as a top-level id and the rest under data[...].

After

  • The per-entity batch route for items/categories is mounted with the :{entityTypeId} segment and threads the validated entityTypeId (positive integer; dedicated-API ids like deals=2 are rejected with a pointer, same as the single routes) into every command — for all actions: create/update/delete and the read actions list/get/fields.
  • Folder batch-create mirrors the single-route shape: id=<parent>&data[...]. A missing parentId is a clean per-item 400 before 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, while title allows 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 menuTitle from the publish body: the field is ignored, the menu item name comes from title and catalogTitle.
  • Keep a catalog app name within 100 characters.
  • Handle the rejections on a rename: on NO_USER_TOKEN authorise the app on the Bitrix24 account, on BITRIX_PARTIAL_REBIND repeat the request.
  • Note the side effect: renaming through title now also writes catalogTitle — 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.