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

Vibecode API Changelog

Vibecode API change history: new features, fixes, and breaking changes. Entries are ordered newest to oldest.

Entry prefixes

  • NEW — a new capability: a new endpoint, a new optional field or parameter, a new error code in a new scenario. Existing client requests keep working.
  • FIX — corrected behavior. The response changes to the correct one, no client action required.
  • BC — a breaking change. Requires client action. The old format is supported for the stated period, then discontinued.

Entry code format: {TYPE}-{MMDD}-{N}, where MMDD is the publication date and N is a running number within that date.

Some NEW entries are marked rolling out: the method shipped in a specific Bitrix24 update and is not yet available on every portal. Until the update reaches a portal, the call returns 422 METHOD_NOT_YET_AVAILABLE with the target version — this is a sign of the rollout, not an integration error.

2026-07-27

NEW-0727-1: app favicon in one line — /_gw/icon

The browser-tab favicon is now set with a single static line that carries no server id:

html
<link rel="icon" href="/_gw/icon">

/_gw/icon is a platform-served path on the app's own origin; it always returns the currently uploaded icon. One upload via POST /v1/infra/servers/:id/icon drives both the Bitrix24 catalog card and the favicon: re-upload the icon and the favicon refreshes on its own (~5 minutes), no rebuild needed. You no longer need to ship your own static icon file in the app. The line is compatible with one-shot app creation (no id required).

Additionally, the POST /v1/infra/servers/:id/deploy response and the one-shot create POST /v1/infra/servers response now return a warnings[] entry when no icon has been uploaded yet — with the exact upload endpoint (the icon is a separate request, since the id only exists after create). The same warnings[] still flags a missing displayName/description. A successful deploy that left the app icon-less or unnamed is no longer silently treated as finished.

NEW-0727-2: source-at-create: SOURCE_AT_CREATE_GALAXY_ONLY now carries a hint with a path to a galaxy

A POST /v1/infra/servers refusal with source that could not be placed in a galaxy (a both-mode portal with no open galaxy host, or a standalone-only portal) now additionally carries error.hint — an actionable path: how to get a galaxy host and/or how to deploy to a dedicated server in two steps. The error code and message are unchanged.

FIX-0727-3: galaxy apps: PATCH /sleep and PATCH /port now return 400 — manage them from the Galaxies page

Before

For an app hosted in a galaxy (GALAXY_APP), PATCH /v1/infra/servers/:id/sleep returned 200 and wrote sleepAfterMinutes, and PATCH /v1/infra/servers/:id/port returned 404/409 — contradicting the documented /v1/me (deployment.galaxyApp) contract, where no V1 lifecycle action applies to a galaxy app.

After

Both calls for a galaxy app return 400 with error.code = "GALAXY_APP_USE_GALAXY_ROUTE" and change nothing. Configure the app's auto-sleep via the galaxy route; a galaxy app's port is host-pinned and not settable. Standalone-server behavior for /sleep and /port is unchanged.

FIX-0727-4: galaxy app recovers after its host tunnel drops

Before

When a galaxy host's secure tunnel dropped (the server still RUNNING, but the connection lost), deploying and running commands for a galaxy app returned 502 GALAXY_HOST_UNREACHABLE, and a logs request returned an empty response with a hint. The host stayed unreachable until a manual repair: retrying the same request hit the same error indefinitely.

After

The platform now restores the host's tunnel in the background without holding up the response. Retrying the same request succeeds as soon as the host reconnects (usually within a minute). Parallel deploys/commands against one shared host do not start a duplicate recovery.

Impact

The error code and response shape are unchanged — 502 GALAXY_HOST_UNREACHABLE (for deploy and exec) is still marked retryable, and logs still return an empty list with a hint. What changed is that a retry now succeeds instead of failing forever. Keep retrying on that code and on the empty logs response with your usual policy.

Affected endpoints: POST /v1/infra/servers/:id/deploy, POST /v1/infra/servers/:id/exec, GET /v1/infra/servers/:id/logs

NEW-0727-5: warning when a deploy changelog has nowhere to be published

The POST /v1/infra/servers/:id/deploy response now carries an entry in warnings[] when the request included a changelog but the deploy produced no new source version. A release note is bound to a version, so without one the text is stored nowhere and never reaches the app's channel feed in the Bitrix24 messenger.

The reason is visible in the source field of the same response: source storage is disabled (feature-disabled-platform or feature-disabled-portal), the save failed (save-failed), or the uploaded bytes matched the previous version. Such a deploy used to answer with a plain success, leaving no way to learn the note was lost. The warning is delivered both in JSON mode and in the done event when ?stream=true is used.

FIX-0727-6: bizproc-templates list without an explicit select now returns every field, including id

Before

GET /v1/bizproc-templates and POST /v1/bizproc-templates/search without an explicit select returned only the documentType field. Without id a client could not issue a follow-up update/delete — the list was useless without a second request carrying an explicit select.

After

Both calls without a select return the full declared field set (id, moduleId, entity, documentType, autoExecute, name, description, modified, isModified, userId). An explicit select works as before. The request format is unchanged.

NEW-0727-7: uploading a file to a knowledge-base document now returns assetMarkdown right away

An upload via POST /v1/note/documents/{documentId}/files used to return only { id }, so getting the ready-to-embed block meant a second call to GET /v1/note/documents/{documentId}/files/{id} — or assembling the [[image fileId=N]] markup by hand.

The response now carries the whole file object — id, documentId, name, size, mimeType, assetType, assetMarkdown — the same shape GET returns. Upload the image, take assetMarkdown from the response, put it into the document body and call PATCH: no second request needed.

The change is additive: id stays in place with the same value, so a client reading only that field keeps working unchanged. If a particular Bitrix24 account returns an object without assetMarkdown, the platform invents nothing — the response carries exactly what Bitrix24 sent.

FIX-0727-8: `ttlSeconds` bounds for access tokens in the machine-readable schema now match the behaviour

Before

The OpenAPI schema for POST /v1/infra/servers/{id}/access-tokens advertised ttlSeconds between 60 seconds and 30 days. The platform, from day one, accepted 300 seconds up to 315,360,000 (ten years) and rejected anything outside that with 400 INVALID_TTL. So a client — or a client library generated from the schema — that took the minimum straight from the schema hit a hard error on a value the schema itself offered, while the "unlimited" option from the interface looked unavailable through the API. The prose documentation was correct all along; only the machine-readable schema disagreed.

After

The schema reads its bounds and default from the very constants the request is validated against, so the two can no longer drift: minimum 300, maximum 315,360,000, default 86,400.

What this means for integrators

The endpoint's behaviour did not change — only what the machine-readable schema says about it. If you generate a client from the OpenAPI document and it validates ttlSeconds on your side, regenerate it: the old client would reject valid values above 30 days and allow values below 300 seconds that the platform always refused.

NEW-0727-9: A placement app can auto-resize its iframe

An app embedded in a placement can now report its content height to the platform, which grows the iframe to fit it — previously the height was fixed by Bitrix24 and tall content was clipped. The app posts a message to its parent window: window.parent.postMessage({ type: 'vibe:resize', height: <pixels> }, '*'). The accepted type is vibe:resize or vibe:setHeight with a numeric height field; targetOrigin must be '*' — the browser checks it against the window's immediate parent. Recompute the height when the content changes, for example with a ResizeObserver. A full description and recommendations are in the "iframe auto-height" section of the app runtime guide.

The feature is activated per Bitrix24 account on the platform side; if the resize does not take effect yet, it is not active for your account.

FIX-0727-10: list offset now counts records, not pages

Before

Bitrix24 returns lists in pages of 50 and reads the offset as a page ordinal, not a record count. We forwarded offset verbatim, so it was silently floored to a multiple of 50: offset=0, offset=7 and offset=49 all returned the same first page — no error, no warning. Walking a selection in steps smaller than 50 records looped on the first page, while a step of exactly 50 worked and made the parameter look healthy.

After

offset counts records on the generic list endpoints: GET /v1/{entity}, POST /v1/{entity}/search, POST /v1/batch (action list) and GET /v1/{entity}/{id}/activities. offset=7 starts at the 8th record. Offset and limit are independent: ?limit=2&offset=51 returns exactly two records starting at the 52nd. Bitrix24 still pages by 50 — Vibecode fetches the page covering the requested position and trims the head; the cost is at most one extra Bitrix24 page per request.

Two consequences are fixed alongside. meta.hasMore accounts for the offset: previously, for entities whose list Bitrix24 returns under a named key — deals, contacts, companies, leads, tasks, orders, products, invoices and others, two dozen in all — it compared only the page length against the total and stayed true on the last page whenever offset was non-zero. GET /v1/{entity}/{id}/activities now honours limit: the underlying Bitrix24 method accepts no size limit, so the whole page used to come back regardless of the requested value.

When the requested position lands past what Bitrix24 returned for the fetched page — because it filters after paginating — the response carries meta.warnings with code OFFSET_BEYOND_FETCHED_PAGE; this used to surface as an unexplained empty list.

For deep paging over large selections a keyset cursor (filter[>id] with order[id]=asc) is still more reliable than an offset: it is depth-independent and stable under concurrent writes.

Impact on integrators

Nothing to change: an offset that is a multiple of 50 behaves exactly as before, and code that already used it that way keeps working untouched.

Two things are worth checking. First, GET /v1/{entity}/{id}/activities with an explicit limit: it used to return the whole page (up to 50 records) regardless of the value, and now returns exactly what was asked for. If your code relied on getting more than it requested in one call, raise the limit or page through the selection. Second, walking a selection in steps smaller than 50: that used to loop on the first page and now moves forward, so a loop that leaned on an extra counter-based exit will start returning new records.

Residual exceptions where the offset is NOT row-exact. First, four generic-layer entities whose Bitrix24 method rounds the offset down to a page boundary and whose selection cannot be widened beyond the requested size without risking lost records: calendar-events, calendar-sections, telephony-lines, workgroups. The offset there is unchanged. Second, individual endpoints with their own handlers, which were out of scope for this change: /v1/warehouses, /v1/bookings, /v1/posts, /v1/requisite-links, /v1/lists, /v1/timeline-logs and the stage history under /v1/crm-extras. Their behaviour is unchanged.

The four generic-layer entities above keep their previous offset behaviour, but their meta.hasMore is now more accurate: on the last page with a non-zero offset it could previously say "no more" while records remained.

FIX-0727-11: a closed feedback ticket no longer reports itself as "in progress"

Before

A ticket caught by probe-campaign detection reported status: NEW to its author regardless of what had actually happened to it. The list filter runs on the real status, so a closed ticket both passed the "Solved" tab filter and rendered an "in progress" badge — the same ticket contradicting itself. Affected GET /v1/feedback and GET /v1/feedback/{id}.

After

Only the state the mask exists for is masked: the auto-archive. A closed ticket returns RESOLVED, a ticket in progress returns its real status, and an auto-archived one still arrives as NEW. Keys with feedback access (management, vibe:feedback scope) see the real status as before.

What this means for integrators

A client that read status and expected NEW for such a ticket now gets its actual status — that is the fix. In addition, on a detection-flagged ticket that is already closed, a withdrawal (PATCH {"status":"WITHDRAWN"}) and an author reply are now rejected with 409 FEEDBACK_CLOSED, like on any closed ticket; they used to be accepted because the gate consulted the masked status.

FIX-0727-12: Connect keys: stored scopes are authoritative — vibe:ai / vibe:search are no longer added automatically

Before

A key issued via VibeCode Connect automatically gained the platform scopes vibe:ai and vibe:search on every request, even when they were neither requested nor consented. Such a key could reach the AI endpoints (/v1/chat/completions, /v1/ai/*) and the Search endpoint (/v1/search), and the spend was charged to the account the portal is bound to.

After

The scopes stored on a key are now authoritative — the platform no longer widens them automatically. A key issued via Connect reaches the AI and Search endpoints only when the corresponding scope is actually present on the key; otherwise the response is 403. Rotating a key preserves this property. Keys created in the cabinet are unchanged.

FIX-0727-13: Apps: a derived key and app-scope sync grant no more than the calling key holds

Before

Calling POST /v1/apps with an authoritative key (issued via VibeCode Connect, or derived from one) minted the app's paired key with the full set of default platform scopes (vibe:infra, vibe:ai, vibe:search, vibe:storage) — even when the calling key held none of them. Likewise PATCH /v1/apps/:id could write a vibe:* scope into the app declaration that the key does not hold. In both cases the derived key gained the ability to reach AI and Search, charged to the account the portal is bound to.

After

A derived key receives exactly the platform scopes the calling key actually holds. When an authoritative key lacks a scope, a request carrying it in the body returns 403 SCOPE_GRANT_REQUIRES_CONSENT, with the unconsented scopes listed in error.details.unconsented. Scopes the key does hold (for example a consented vibe:storage) pass through as before. The derived key inherits the caller's expiry. App-scope synchronisation to paired keys no longer adds vibe:* to an authoritative key — scope narrowing still applies. Keys created in the cabinet are unchanged: they still receive the default platform scopes.

FIX-0727-14: Bitrix24 call timeout is now configurable, the internal retry after a timeout is removed

Before

The platform always aborted an HTTP call to Bitrix24 at the 15-second mark, and for read methods issued one internal retry after the abort — up to ~30 seconds before the 503 BITRIX_TIMEOUT response. The retry also started a second concurrent execution of the same call on the portal: dropping the connection does not stop Bitrix24 from processing the request.

After

  • The time limit of a single Bitrix24 call is now platform-configurable (default stays 15 seconds). On portals where Bitrix24 responds slowly the platform can raise the limit — requests that previously ended in a stable 503 BITRIX_TIMEOUT now live to the real answer and return data.
  • The internal retry after a timeout is removed for all methods. 503 BITRIX_TIMEOUT on reads arrives roughly twice as fast (~15 seconds instead of ~30), and the request is no longer executed on the portal twice. 429 (rate limit) retries are untouched.
  • The error text Bitrix24 did not respond within 15s now carries the actual limit (e.g. within 60s) — do not rely on the constant in the text.

2026-07-25

NEW-0725-1: deploy: optional changelog field

POST /v1/infra/servers/:id/deploy accepts a new optional changelog field — plain text up to 2000 characters describing what changed in this version. When a new app version ships, the text is posted to subscribers in the app's Bitrix24 messenger channel feed; if the field is omitted, only the version number is posted. The field works in both JSON and multipart deploy modes. Existing deploy calls keep working unchanged.

NEW-0725-2: model calls with a short-lived token issued to a partner system

Before

The AI router endpoints /v1/chat/completions, /v1/embeddings, /v1/audio/transcriptions and /v1/models accepted a regular platform key only.

After

The same endpoints (and their /v1/ai/... aliases) now also accept a short-lived token of a new type. A Bitrix24 partner system obtains it over its own signed channel; the token is bound to one account and one employee, lives for one hour, and is admitted to exactly these eight routes — any other path answers SCOPE_FORBIDDEN. Spend on such a token is metered by the platform itself and charged synchronously against the account AI quota, so an exhausted quota is refused before the model is called. Only models enrolled in the quota program are available — /v1/models under the same token returns that list.

Refusal codes this route can now return: TOKEN_INVALID, SCOPE_FORBIDDEN, MODEL_NOT_IN_QUOTA_PROGRAM, CREDENTIAL_NOT_PLATFORM, ACCOUNT_FROZEN, PORTAL_DELETED, PORTAL_BLOCKED, PORTAL_SUSPENDED. The envelope is unchanged: success plus error with code and message.

Regular platform keys are unaffected: without a token of the new type the responses are byte-identical to before.

2026-07-24

FIX-0724-1: activities: an unrecognized filter field name is now rejected with 400 UNKNOWN_FILTER_FIELD

Before

GET /v1/activities and POST /v1/activities/search silently dropped an unknown filter key: the request returned 200 success with the filter trimmed to empty — that is, it returned the whole (owner-scoped or entire) activity set. For example {"filter":{"ownerTypeId":2,"ownerId":3,"bogusField":123}} ignored bogusField and returned every activity of the parent deal. This diverged from the documentation and from other CRM entities (companies, invoices), where such a filter was already rejected.

After

A filter field name that is not in the activity schema — and is not a UF_* custom field, the id key, or a special token — is now rejected before the Bitrix24 call with 400 UNKNOWN_FILTER_FIELD and a list of available fields, the same as companies/quotes/contacts already do. The full list of filterable fields is returned by GET /v1/activities/fields.

Impact on integrators

Filtering by real activity fields (in camelCase or the native Bitrix24 UPPER case), by UF_* fields, operators (>=, <, !, etc.), ranges, and AND/NOT all work as before. If you relied on the silent drop of an unrecognized key, remove it from the filter.

NEW-0724-2: bizproc activities and robots /fields now return field labels and descriptions

Before

GET /v1/bizproc-activities/fields and GET /v1/bizproc-robots/fields described each field only by its type and the readonly flag, with no human-readable labels.

After

Each of the 12 fields now carries a label and a description in the segment language, which makes building forms and hints easier. Field types are unchanged.

FIX-0724-3: Downloading your own storage objects no longer returns 403

Before

GET /v1/storage/objects/:key could return 403 for an object the key owner legitimately owns but that physically lives under a different storage "family" prefix (for example, server files visible in a developer's listing). The object appeared in the listing, yet downloading it, minting a presigned URL or issuing a HEAD failed.

After

The scoped credentials account for the object's actual family (the portal stays bound to the caller's context), so download (?download), streaming (?inline) and HEAD work for your own objects regardless of family. Ownership checks are unchanged — someone else's object still returns 404.

FIX-0724-4: GET /v1/workflows honours the limit parameter

Before

GET /v1/workflows accepted limit but silently ignored it — the whole page of running workflows (up to 50) was always returned regardless of the requested size.

After

limit is respected: the response holds at most the requested number of records. Values above 50 are gathered page by page (capped at 500); meta.total still reports the total number of running workflows.

FIX-0724-5: deploy: an app wrapped in a single archive folder no longer fails with ENOENT package.json

Before

Deploying (POST /v1/infra/servers/:id/deploy) to a standalone server an archive that wraps the project in a single top-level folder (e.g. myapp/package.json instead of package.json at the root) failed at the install step:

npm error enoent Could not read package.json ... open '/opt/app/package.json'

The uploaded archive was left beside the extracted content, so the auto-flatten of a single wrapping directory did not fire (the root held two entries — the archive and the folder) and package.json stayed nested.

After

The uploaded archive is removed before the flatten step, so a single wrapping directory collapses, package.json lands at the deploy root, and the install proceeds normally.

Impact

No client action required. Flat archives (files at the archive root) work as before; to be safe you can package flat: tar -czf build.tar.gz -C <project_dir> ..

FIX-0724-6: /v1/users* endpoints no longer return 403 and 500 on keys with user access

Before

On a read-only (READONLY) key with user access, GET /v1/users/me returned 403 WRITE_BLOCKED_READONLY_KEY even though it is a read endpoint. Separately, GET /v1/users, GET /v1/users/:id, POST /v1/users/search and GET /v1/users/fields returned 500 INTERNAL_ERROR on accounts where one of the user fields (UF_*) had an empty definition.

After

GET /v1/users/me works on a read-only key. The other /v1/users* endpoints return data and skip the field with an empty definition instead of failing.

Impact on integrators

No action required — existing calls keep working, and the previously failing scenarios now respond correctly.

NEW-0724-7: bizproc activity and robot callback delivery to a Black Hole app

Registering a bizproc activity or robot whose handler points at your Black Hole deploy server now reliably delivers the execution callback (event token, auth block, code and properties) to the app. Previously that callback could be lost: Bitrix24 online events are not retried, and a sleeping or waking server dropped the call. The platform intercepts the handler at registration, queues the call durably, and retries delivery with a server wake-up and backoff.

Affects POST /v1/bizproc-activities and POST /v1/bizproc-robots (and their update). New error code SERVER_APP_MISMATCH (400): the Black Hole server behind the given handler must belong to the same app that registers the activity. Registering such a handler through /v1/batch is not supported — use a single request (BIZPROC_CALLBACK_BATCH_UNSUPPORTED). Additionally, POST /v1/bizproc-robots now requires code, name and handler upfront — omitting any returns 400 MISSING_REQUIRED_FIELDS instead of a raw Bitrix24 error (as activities already did).

The capability is rolling out gradually and is enabled per account: until it is on for your account, registration behaves as before, without managed delivery. Once it is on, batch registration behaviour changes: an attempt to register a BH handler via /v1/batch starts being rejected (BIZPROC_CALLBACK_BATCH_UNSUPPORTED) — move such registrations to a single POST /v1/bizproc-activities or /v1/bizproc-robots.

2026-07-23

NEW-0723-1: app blueprints library — spec via API key

Ready-made specs for popular apps are now available via a key: GET /v1/app/blueprints/:slug?locale=ru|en returns the raw spec markdown (Content-Type: text/markdown). The endpoint requires Authorization: Bearer <key>; an unknown or hidden blueprint returns 404 BLUEPRINT_NOT_FOUND. The copy-paste "AI prompt" shown when creating a key carries the spec link — the AI agent fetches the spec with the same key. The former anonymous path /api/public/blueprints/:slug.md has been removed.

NEW-0723-2: sources of a deleted server: access, cleanup, and an honest answer for purged bytes

Sources outlive their server — a long-standing platform guarantee — but the API gave you no way to reach them: the whole /v1/infra/servers/:id/sources* surface answered 404 for a deleted server, so the owner could neither list their versions, nor untag them, nor delete them. For a version tagged published or manual that was a dead end: untagging is the only sanctioned way past 409 PROTECTED_BY_TAG, and untagging was exactly what you could not do.

Read and cleanup verbs now work on a deleted server: version list, version metadata, download, tag, PATCH, DELETE and cleanup. Saving a new version (POST /sources) still answers 404 — a dead server accepts no new deposits.

So that a deleted server can be found at all, GET /v1/infra/servers accepts ?includeDeleted=true. The default listing is unchanged. Every row now carries a deletedAt field (null for live servers).

Separately: a version whose bytes were already purged from storage now answers 410 with code SOURCE_VERSION_BYTES_PURGED instead of 404. The difference matters — 404 claimed the version did not exist, when in fact its record is alive and the recovery is different: re-upload the archive rather than look for it elsewhere. The code arrives on download and on deploy by {"source": {"versionId": "vN"}}.

Affected endpoints: GET /v1/infra/servers, POST /v1/infra/servers/:id/deploy, GET /v1/infra/servers/:id/sources, GET /v1/infra/servers/:id/sources/:versionId/download — the sources contract lives on the Source storage page

FIX-0723-3: galaxy app deploy: a mid-build tunnel drop is no longer masked as "host unreachable"

Before

If the host tunnel blipped during the build and no healthy container of this deploy resulted (the build was interrupted, the container never came up, the exec channel was busy, or the app crashed), the deploy (POST /v1/infra/servers/:id/deploy) returned 502 GALAXY_HOST_UNREACHABLE advising "retry once it reconnects". The host was often reachable — the advice was misleading, and the caller never saw the real cause (for example, their own app failing to start).

After

When the host is reachable after the blip but the deploy did not bring the app up, the deploy returns a new code 502 GALAXY_DEPLOY_INTERRUPTED — "the host is reachable, but the deploy was interrupted before the app started: re-send the same deploy; if the app repeatedly fails to start, fix it first (the start command, port, dependencies, environment variables, or memory limit)". It stays retryable — the slot is intact, no need to delete and recreate it. The GALAXY_HOST_UNREACHABLE code now stays only for a genuinely unreachable host (no probe reached it). If the app truly crash-loops, that is reliably surfaced on the retry by the normal liveness check (code GALAXY_APP_START_FAILED).

NEW-0723-4: deploy step timeout error now carries a recovery hint

The DEPLOY_TIMEOUT error from POST /v1/infra/servers/:id/deploy now carries a structured error.hint object (reason, recovery, recoveryAction) tied to the timed-out step (error.step). For user commands (install, preStart) the hint explains that the command did not finish within its time budget and advises making it non-interactive and self-exiting, and launching long-running services from the start command or detached (docker compose up -d). For the runtime install step (runtime — a platform step, not a user command) and other service steps it points at a possible tunnel stall and POST /v1/infra/servers/:id/repair. The field is additive: the existing error.code, error.message and error.step are unchanged, no integration change is required; the hint arrives in both JSON mode and the SSE error event.

FIX-0723-5: a failed deploy step now shows the real cause, not a benign warning

Before

When a deploy step failed, data.steps[].stderr (and hence error.message) could carry only a benign one-stream warning, losing the real cause of the failure.

After

Both streams are returned together, labelled stderr: and stdout:; the real cause is no longer hidden. The response shape and field name are unchanged.

FIX-0723-6: list sorting by a non-unique field no longer drops records on the second page

Before

A list or search sorted by a non-unique field (for example the date field begindate), over more than 50 records, could silently return fewer records than exist: at the page boundary some records sharing the same sort-field value were lost. The response was a 200 with no incompleteness signal. Affected the CRM smart-process-backed entities — deals, leads, contacts, companies, quotes, invoices and smart-process items /v1/items/{entityTypeId} — on GET /v1/{entity}, POST /v1/{entity}/search, inside POST /v1/batch sub-calls and per-entity POST /v1/{entity}/batch. The same instability class affected numeric aggregation (POST /v1/{entity}/aggregate with sum/avg/min/max/groupBy): the record fetch for the aggregate ran with no order, so over more than 50 records some rows could be lost and skew the result.

After

A secondary id key is appended to the sort, making the order fully deterministic, so paginated reads no longer drop or duplicate records regardless of the sort field. Your sort stays the primary key; records with an equal sort-field value are ordered by ascending id. No request changes are needed.

FIX-0723-7: reopening a ticket via a comment no longer leaves the resolution stamp

Before

A team comment through POST /v1/feedback/:id/comments that moved a ticket from RESOLVED or WITHDRAWN back into an active status (NEW, REVIEWING, AWAITING_USER, NEEDS_REVIEW) did not reset resolvedAt and resolvedBy. They lingered from the prior close, so on reads (GET /v1/feedback/:id, GET /v1/feedback) a reopened ticket looked both active and resolved.

After

Such a comment clears resolvedAt and resolvedBy — on reads an active ticket no longer carries a resolution date. resolution is left as is: it mirrors the comment body. A comment that sets RESOLVED still stamps the fields; moving to ARCHIVED and a plain transition between active statuses leave the stamp untouched. This aligns the behaviour with the clear already applied on PATCH /v1/feedback/:id.

FIX-0723-8: list filters on statuses / departments / storages / currencies / products are no longer silently ignored

Before

GET /v1/statuses, /v1/departments, /v1/storages, /v1/currencies, /v1/products run on legacy Bitrix24 methods (crm.status.list, department.get, disk.storage.getlist, crm.currency.list, crm.product.list) that silently ignore non-filterable keys and operators. An unknown or unsupported filter field — e.g. filter[system] on statuses, filter[module] on storages, or filter[price] on products — as well as operators $gt / $contains / $ne returned 200 with the whole table. The client received the full set instead of the expected subset — a silent failure with wrong data.

After

For these entities the filter is validated before the Bitrix24 call: only fields the method actually filters on (live-verified) are allowed. Any other field, operator, or empty set returns 400 UNSUPPORTED_FILTER listing the filterable fields. Allowed fields per entity: statuses — id, entityId, statusId, name, sort, semantics, categoryId; departments — id, name, parentId, headId; storages — id, name, code, entityType, entityId; products — id, name, code, xmlId, active, sectionId, sort. crm.currency.list filters on nothing — any filter on /v1/currencies returns 400 with a hint to filter client-side.

BC-0723-9: POST /v1/apps no longer returns the prefix and suffix fields in the create response

Old format supported until: 21.07.2026

Before

The POST /v1/apps create response carried two vibe_app_ values — a short prefix and the full rawKey. The short prefix was mistaken for the key, and a request using it returned 401.

After

The create response carries one vibe_app_ value — the working rawKey. The prefix and suffix fields remain on GET /v1/apps and GET /v1/apps/:id for masked-key display.

What integrators should do

Use the rawKey field from the create response as X-Api-Key. If you need the masked prefix, read it from GET /v1/apps or GET /v1/apps/:id instead of the create response.

FIX-0723-10: POST /v1/batch now persists phone and email on lead and contact create and update

Before

Through the shared POST /v1/batch the phone and email (multifield) values were dropped on a lead or contact create or update. The call returned success but the field was not saved. The same payload through the single POST /v1/leads or PATCH /v1/contacts/:id and through POST /v1/{entity}/batch saved correctly.

After

The shared POST /v1/batch serializes multifields the same way the single calls do. Phone and email persist on create and update.

FIX-0723-11: Search and research no longer return 402 INSUFFICIENT_BALANCE on a funded account

Before

POST /v1/search and POST /v1/research with the platform-managed engine (bitrix-search) on a personal key could return 402 INSUFFICIENT_BALANCE even with enough Vibe balance on the account. The pre-flight balance check looked the account up by the key owner, while the account is now one per portal — so it was not found.

After

The balance check and charge always resolve the account by the portal. With a positive balance the request runs and is charged correctly; 402 is returned only on a genuine shortfall. The request and response shape are unchanged.

FIX-0723-12: POST /v1/apps reports the plan requirement clearly on the cloud-shared path

Before

Creating an app via the cloud-shared key-issuance path (the unified cloud↔box scheme, rolled out per portal cohort) on a portal without the required Bitrix24 plan made POST /v1/apps return an opaque 502 CONNECTOR_APP_INSTALL_FAILED with no cause.

After

A plan-access denial is now classified up front: before calling the connector, POST /v1/apps checks the portal's access state and, when it's missing, returns 403 INT_TARIFF_REQUIRED right away, with a readable message. Other cloud-shared issuance failures (module not installed, forbidden by the portal admin, other errors) are classified as before.

Impact on integrators

No action required, successful calls are unchanged. If you handled 502 CONNECTOR_APP_INSTALL_FAILED on app creation, also handle 403 INT_TARIFF_REQUIRED and prompt the user to upgrade their Bitrix24 plan.

FIX-0723-13: starting a server no longer errors when the machine is already running

Before

POST /v1/infra/servers/:id/start for a server in the error state asked the cloud to start the machine and surfaced any rejection as 502 PROVIDER_ERROR. When the machine was already running — for example, brought back by automatic recovery after preemption — the cloud rejected the call with «instance already in RUNNING state», and the endpoint returned an error for an operation that had in fact succeeded. Clients saw a 502 and could not tell it apart from a genuine failure.

After

That rejection is now treated as an idempotent success: when the machine is already running or in a transitional state, the call returns 200 and the server moves to provisioning, exactly as on a normal start. Genuine failures — insufficient permissions, exhausted quota, machine not found — still return 502 PROVIDER_ERROR.

This is the same idempotency criterion the agent start endpoint and the internal server wake path already applied.

BC-0723-14: the /v1/me deployment.standalone.requiredFields.create shape is now an object + documents the name slug

Old format supported until: 22.01.2027

Before

In the GET /v1/me response, the per-kind sub-block deployment.standalone.requiredFields.create was an array ["provider", "name", "plan", "region"] — the format of name was not stated; the sibling deployment.galaxyApp.requiredFields.create said only "required" for name. A name with non-Latin or uppercase characters was rejected by POST /v1/infra/servers with 400 INVALID_REQUEST, but self-discovery never surfaced that constraint.

After

deployment.standalone.requiredFields.create is now an object (like its sibling deployment.galaxyApp.requiredFields.create), and in both sub-blocks name carries its format: a lowercase-Latin slug matching ^[a-z][a-z0-9-]*$, 2–63 characters long. Put a human-readable label in the optional displayName field.

What integrators should do

The flat deployment.requiredFields["POST /v1/infra/servers"] (the array ["provider","name","plan","region"]) is unchanged — if you read it, no action is needed and the set of required fields is the same. If your code parsed the per-kind sub-block deployment.standalone.requiredFields.create as an array (.forEach / .includes("name") / .length / [0]), switch to reading it as an object: the keys are field names (provider/name/plan/region) and the values are their descriptions.

NEW-0723-15: GET /v1/contacts/fields now returns label and description for every field

The GET /v1/contacts/fields response now carries human-readable label and description for all 28 static contact fields. Previously the base fields (name, lastName, typeId and others) came back with only type and readonly, with no explanation of their meaning. Labels are localized to the segment language (English on .com). You can read a field's semantics programmatically from the response instead of cross-referencing the static documentation. In addition, GET /v1/openapi.json exposes these labels and descriptions (in English) as title and description annotations in the Contact and ContactInput schemas.

NEW-0723-16: smart-processes: relations and linkedUserFields fields in the input schema

The relations (parent/child CRM entity links, e.g. linking a smart process to deals) and linkedUserFields fields are now declared in the input schema and shown in GET /v1/smart-processes/fields. Pass them in POST /v1/smart-processes and PATCH /v1/smart-processes/:entityTypeId to link a smart process to other CRM entities and surface it in user fields. Filtering and sorting by these fields are not supported — they are nested write structures, not query fields.

FIX-0723-17: bizproc-activities and bizproc-robots: documentType type in /fields corrected to array

Before

GET /v1/bizproc-activities/fields and GET /v1/bizproc-robots/fields reported documentType as type object, while the field is a three-element array ([moduleId, entity, documentType]), as already declared for bizproc-templates.

After

The documentType type in /fields is now array across all three entities — consistent with the real contract.

FIX-0723-18: PATCH /v1/bizproc-templates returns a numeric id

Before

PATCH /v1/bizproc-templates/:id returned data.id as a string ("1215"), while POST returns a number (1215). A client comparing the id from the create response with the update response saw a false mismatch.

After

The PATCH response returns data.id as a number (1215) — the same as POST.

FIX-0723-19: userfields: label type in the create schema corrected to string

Before

The OpenAPI schema for POST /v1/userfields/{entity} declared label as an object. B24 crm.<entity>.userfield.add accepts LABEL as a string only, so an SDK generated from the spec (where label was an object) sent the wrong type and failed. The OpenAPI spec is a public contract — clients generate SDKs from it, and anyone whose type was "object" had a broken client.

After

label in the create schema is declared as string (portal default-language label). Multilingual labels are set via editFormLabel / listColumnLabel / listFilterLabel (PATCH after create). No runtime change — only the generated spec was corrected.

FIX-0723-20: sleep-now on a galaxy app now returns 400 — manage it from the Galaxies page

Before

POST /v1/infra/servers/:id/sleep-now on a galaxy-hosted app (GALAXY_APP) put the container to sleep and returned 200. This diverged from the session route, which already rejected such an app, and could desync the container state from its host.

After

The same call on a galaxy app returns 400 with error.code = "GALAXY_APP_USE_GALAXY_ROUTE" and changes no state: the container stays RUNNING. Manage the app's lifecycle through the galaxy routes instead. Standalone servers keep their existing sleep-now behavior.

FIX-0723-21: a task comment is no longer served under a foreign task

Before

On legacy portals GET /v1/tasks/:taskId/comments/:id returned 200 with the comment even when the comment did not belong to task :taskId: the same comment was served under any task, and the response taskId was a plain echo of the path.

After

The comment is checked against the task in the path before it is returned. If the comment does not belong to :taskId, the endpoint responds 404 with code NOT_FOUND and message "Comment not found". The response taskId now matches the real parent task. Fetching a comment through its real task keeps working unchanged.

FIX-0723-22: company type is a single field typeId, not companyType

Before

The «company type» field name differed across layers. POST /v1/companies with a companyType field silently ignored the type — the company was created with the default type; the only way to set it was the typeId field. Reads (GET, search) always returned the type in the typeId field. The filter, however, accepted companyType but not typeId.

After

Company type is a single typeId field across all operations: create and update, read and search, filter (filter[typeId]) and grouping (groupBy: typeId). The values are unchanged — CUSTOMER, SUPPLIER, COMPETITOR (list: GET /v1/statuses?filter[entityId]=COMPANY_TYPE). The field is now described in GET /v1/companies/fields.

Impact on integrators

Set the type with the typeId field. Reads are unchanged — the type was always returned in typeId. On create and update companyType is no longer documented (it never persisted a value). In filters and grouping typeId now works, while companyType returns 400 (UNKNOWN_FILTER_FIELD in filters, INVALID_AGGREGATION_FIELD in grouping) — replace the name with typeId.

2026-07-22

BC-0722-1: server icon is now served as PNG

Old format supported until: 21.07.2026

The server icon is now served as a 256×256 PNG (Content-Type: image/png) — the platform renders it from your uploaded SVG. The upload endpoint (POST /v1/infra/servers/:id/icon) still only accepts SVG, and may now return 400 ICON_RASTERIZE_FAILED when the file can't be rasterized to PNG.

NEW-0722-2: scheduled wake (wake-schedules) is now available on every Bitrix24 account

The wake-window CRUD — GET|POST /v1/infra/servers/:id/wake-schedules, PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId — is now available on every Bitrix24 account for standalone servers (kind: "STANDALONE"): the call no longer returns 403 WAKE_SCHEDULE_DISABLED. The platform brings a sleeping server up by a given cron-expressed moment, and the task itself is fired by the app's own cron once the machine is already up. Galaxy apps (kind: "GALAXY_APP") are not part of this rollout yet and still return 403 WAKE_SCHEDULE_GALAXY_DISABLED. The GET /v1/infra/servers/:id response and the server list now include the additive fields nextScheduledWakeAt (the next scheduled wake time, ISO 8601 or null) and wakeScheduleCapable — existing fields are unchanged.

NEW-0722-3: deploy 409 SERVER_NOT_READY now signals it is retryable

POST /v1/infra/servers/:id/deploy now adds error.retryable: true, error.retryAfter (seconds) and a Retry-After header to the 409 SERVER_NOT_READY response returned when the server is already restoring its connection (a repair started by a concurrent deploy or repair). It is a machine signal that retrying is worthwhile: wait the given interval and re-issue the deploy. Existing clients are unaffected — the error code and message are unchanged, the fields are additive.

NEW-0722-4: New endpoint to activate the Marketplace trial for the key's portal

POST /v1/portals/:id/activate-market-trial activates the one-time Bitrix24 Marketplace trial for the portal the calling key belongs to. Previously activation was only possible from the web cabinet — API keys had no programmatic path.

:id must match the key's portal, otherwise 403 PORTAL_MISMATCH. No request body. Rate-limited to 3 requests per hour per portal.

Success response: { "success": true, "data": { "status": "activated", "trialEndsAt": "..." } } (or "status": "already_active" when the trial/access already exists). Errors: 403 WRITE_BLOCKED_READONLY_KEY (read-only key), 403 PURPOSE_KEY_FORBIDDEN (special-purpose key may not activate the trial), 404 NOT_FOUND (portal not found), 409 ALREADY_ACTIVATED (trial already activated), 409 TRIAL_ACTIVATION_UNAVAILABLE (trial not available for this portal), 503 TRIAL_ACTIVATION_RETRY (transient error, retry later).

FIX-0722-5: the portal plan probe no longer stops at a scope-limited developer key

Before

On a portal with several developer-key holders the plan state could stay unread: if the first probed key answered with portal data but without the full licence block (a scope-limited key), the probe stopped there and never reached a key able to read the rest. The plan state stayed unknown, so a portal entitled to create servers could receive 402 from POST /v1/infra/servers while GET /v1/me reported capabilities.servers.create.available: false. Calling GET /v1/me?refresh=tariff did not clear the denial.

After

The probe keeps going until a key returns the full licence block. An entitled portal is allowed to create servers and capabilities.servers.create.available becomes true. Response shapes are unchanged and no client action is required. The state refreshes with the portal's next plan refresh (within an hour) or immediately via GET /v1/me?refresh=tariff.

Integrator impact

No action required. A client branching on 402 at server creation keeps working — on affected portals that response simply stops arriving.

FIX-0722-6: the `region` field in infrastructure responses always returns a region id

Before

For a server created and running on the international segment, GET /v1/infra/servers and GET /v1/infra/servers/:id could return an internal placement-zone identifier in the region field rather than a catalog region id. The value matched no id in GET /v1/infra/providers/:providerId/regions, so a server could not be matched to its catalog region through that field, and it exposed internal placement detail.

After

region always carries a neutral region id from the same namespace as the catalog — for example bc-eu-central. The value equals the id of the corresponding GET /v1/infra/providers/:providerId/regions entry, so a server maps to its catalog region directly. The placement zone is an internal detail: server creation takes a region, and the platform picks the zone inside it.

What integrators should do

Nothing, if you pass a region value taken from the regions catalog — that flow is unchanged. If your code compared a server response's region against a string obtained earlier from a server response rather than from the catalog, that comparison is now reliable: both ends are in one namespace. Identifiers from the previous catalog are still accepted on input.

2026-07-21

FIX-0721-1: built-in search engine name and description, cloud provider name

The public name of the platform search engine now matches the product name: GET /v1/search/providers and /v1/me return Bitrix24 AI Search in the name field (latin locales). The provider identifier bitrix-search is unchanged — no client action is required.

The source-citation claim was removed from the same provider's description: the capability depends on the engine bound to your instance and is declared machine-readably in capabilities.output.citations of the same response.

GET /v1/infra/providers on the international segment now returns the Bitrix24 Cloud brand in the name field instead of Bitrix Cloud. The provider identifier bitrix-cloud is unchanged.

Before

"name": "Bitrix AI Search" · "description": "Platform AI search with agentic mode and source citations" · "name": "Bitrix Cloud"

After

"name": "Bitrix24 AI Search" · "description": "Platform AI search with agentic mode" · "name": "Bitrix24 Cloud"

FIX-0721-2: creating a business-process template now accepts the template file

Before

POST /v1/bizproc-templates answered 422 Incorrect field TEMPLATE_DATA! for any body — creating a template was impossible: the field holding the .bpt file content was not in the entity schema and never reached Bitrix24.

After

The templateData field (a .bpt file as a [filename, base64 content] array) is accepted and forwarded to Bitrix24, and the template is created. The field is required on create: without it the request is rejected with 400 MISSING_REQUIRED_FIELDS before Bitrix24 is called (previously the raw Incorrect field TEMPLATE_DATA! error came back). It is write-only and surfaces in the field reference at GET /v1/bizproc-templates/fields, but is not returned on reads.

NEW-0721-3: Aggregated source registry — GET /v1/me/sources

A new endpoint GET /v1/me/sources — the programmatic twin of the cabinet «Application sources» page. It returns source snapshots across every server and application the key owns (an account-admin key sees the whole account), with pagination (page/limit/search) and the standard { success, data, total, page, limit } envelope. Each row carries a drill-in pointer — listEndpoint and latestDownloadEndpoint — plus reachableViaApi and, for server rows, blackholeStatus. Unlike GET /v1/infra/servers, which is scoped to the calling key's own servers, this registry also spans a server bound to another key of the same owner.

The server-scoped source endpoints (POST /v1/infra/servers/:id/sources and the sibling list/download/tag/cleanup routes) are now documented, and the versions[].serverContext field ({ serverId, serverName, serverDisplayName, linkedApp }) is formalized in the contract.

NEW-0721-4: optional error.b24Code field in 422 BITRIX_ERROR responses

422 BITRIX_ERROR responses now include an optional error.b24Code field — the raw Bitrix24 error code for programmatic handling (for example, PERIOD_REQUIRED, INVALID_FILTER). The change is additive: existing clients that parse only error.code and error.message are not affected.

NEW-0721-5: Open Channels statistics — 6 dashboard methods

A new API section for contact-center dashboards: POST /v1/openlines/stats (period aggregates), GET /v1/openlines/operators (real-time operator load), POST /v1/openlines/sessions/search, POST /v1/openlines/sessions/stats, POST /v1/openlines/sessions/transfers, POST /v1/openlines/ratings/search. Requires the imopenlines scope and the report_open_lines plan feature (otherwise 403 B24_TARIFF_RESTRICTION).

Rolling out — the methods ship with the Bitrix24 update imopenlines 26.700.0 and are not yet available on every account. Until the update reaches your account, the methods return 422 METHOD_NOT_YET_AVAILABLE with the target version in the response — this indicates the rollout, not an integration error.

FIX-0721-6: a Bitrix24 plan refusal is returned as 403 B24_TARIFF_RESTRICTION on every endpoint

Before

A Bitrix24 refusal caused by a plan restriction arrived as 422 BITRIX_ERROR with an opaque message — there was no way to tell it apart from other Bitrix24 errors programmatically.

After

Such a refusal is returned as 403 with the B24_TARIFF_RESTRICTION code. The rule is platform-wide, not limited to Open Channels: any endpoint that calls a Bitrix24 method unavailable on the Bitrix24 account plan now answers with this code.

Impact on integrators

Clients with generic error handling keep working unchanged — the refusal is still an error, only a more precise one. If you branched on 422 specifically for plan refusals, move that branch to 403 and error.code === 'B24_TARIFF_RESTRICTION'. This code does not mean the integration is broken: the capability is not included in the Bitrix24 account plan, and retrying is pointless until the plan changes.

FIX-0721-7: Versioned deploy runtimes install the advertised version on Ubuntu 24.04

Before

The node20 runtime installed Node.js 18 (Ubuntu 24.04 has no Node 20 package), and python311 plus the RAG runtimes (node20-rag, python311-rag) failed at the install step because the packages are absent from the distribution. The GET /v1/infra/runtimes packages field advertised postgresql-14 while PostgreSQL 16 was installed.

After

node20 now installs Node.js 20 (with a major-version check), python311 installs Python 3.11, and the RAG runtimes install PostgreSQL 16 with the pgvector extension in the application database. The packages field reflects the real version (postgresql-16). Runtime identifiers and the /deploy request format are unchanged.

FIX-0721-8: server repair status is correct when polled

Before

Polling GET /v1/infra/servers/:id/repair-status on a multi-node backend could briefly return idle even while the repair was still running — when the request landed on a different serving node than the one running the repair. A client polling the status in a loop could therefore wrongly conclude the repair had finished before it even started.

After

The endpoint reliably returns the real repair progress (running / done / failed) regardless of which node the poll lands on.

Impact on integrators

The response shape is unchanged and no client action is required.

FIX-0721-9: apps on a standalone server no longer run with administrator privileges

Before

An app deployed to a standalone Black Hole server ran with administrator privileges and no isolation. Any vulnerability in the app itself (remote code execution, for example) immediately meant full control of the whole virtual machine: the server's connection keys, its service configuration and system files.

After

The app runs under a dedicated unprivileged account and sees only its own directory (extractTo, /opt/app by default), which it owns. System directories are read-only to it and privilege escalation is blocked. Ports below 1024 still work — the platform grants the specific permission needed.

Deploy commands (install, preStart) and /exec still run with administrator privileges. Nothing changed there and no sudo is needed.

No action required: if your app genuinely needs administrator privileges to start, the deploy automatically restores the previous mode, finishes successfully and adds a warning explaining why. To skip that attempt outright — relevant for nginx as the start command, MySQL over the root system socket, and Docker-driven starts — pass "hardening": "off" in the deploy body.

Two new step values appear in data.steps[]: service_user for handing the deploy directory to the unprivileged account, and hardening for the warning that the app was restored to the previous mode. Clients that switch on step names should account for them.

BC-0721-10: a broken image_url candidate no longer fails the whole request

Old format supported until: 21.01.2027

Before

A content array could carry several image_url parts. If any one of them held something other than an image — for example a Base64-encoded HTML error page declared as image/png — the platform forwarded it to the model unchanged. The model could not decode it and the entire request failed with 502 and code ai_provider_unavailable, even when the remaining images were valid.

Separately, parts with an unsupported MIME type, malformed Base64, or above the 20 MiB limit were rejected with 400 invalid_image_payload — also for the whole request.

After

Before calling the model, the platform inspects the actual content of every image_url part by its byte signature rather than its declared MIME type. A part whose content is a web response (HTML, XML, JSON, an HTTP response) or does not decode is replaced in place with the text placeholder [image unavailable: <reason>]. The remaining images are processed normally and the request succeeds.

Positions are preserved: the length of the content array does not change, so any candidate numbering on your side stays correct.

Rejected parts are visible in the response — warnings gains an entry with code IMAGE_CONTENT_REJECTED, and the X-Image-Parts-Rejected header carries their count. For streaming responses the header arrives with the start of the stream.

400 is now returned only for structural errors: a missing url field, a string that is neither a URL nor a data URI, an unsupported scheme, and http:// in production.

What integrators should do

If your code relied on 400 invalid_image_payload to detect a rejected image, read warnings or the X-Image-Parts-Rejected header instead. The request now succeeds, and no image is dropped silently — every substitution is reflected in the response.

Model-side failures changed too. Previously any non-2xx provider response arrived as 502; now the status reflects the cause:

  • a model response of 400 or 422400 with code ai_provider_rejected. Retrying such a request unchanged will not help;
  • a model-side rate limit (429) → 429 with a Retry-After header. Retry it after the stated delay. A streaming response cannot carry the header, so the delay arrives as a retryAfter field in the error frame;
  • a model-side timeout (408) → 503 with code ai_provider_timeout.

Responses 401, 403 and 5xx still arrive as 502. This affects POST /v1/chat/completions and POST /v1/embeddings.

Model-side errors now additionally carry a providerStatusCode field — the raw HTTP status of the model response. It tells a model-side 429 apart from the platform's own rate-limit 429 (which has no such field): both keep the same rate_limit_exceeded code so SDKs retry uniformly, and the new optional field is the distinguisher.

Data URIs may now also carry parameters between the type and ;base64data:image/jpeg;name=photo.jpg;base64,... is no longer rejected.

2026-07-20

NEW-0720-1: Application responses now return the publication status

Application responses — list, app data, create, publish, and unpublish — now carry two new fields: catalogStatus (PRIVATE / PUBLISHED / UNPUBLISHED) and publishedAt (the publication date, ISO 8601, or null). Previously the publication status could not be read via V1 — you had to guess from the placements array, which is unreliable: an unpublished application may keep its previously bound codes, and PRIVATE and UNPUBLISHED are indistinguishable by placements. The fields are additive — existing calls keep working.

FIX-0720-2: chat rename no longer reports a false success to a non-participant

Before

PATCH /v1/chats/:chatId called by a portal administrator who is not a member of the chat returned { "success": true, "data": true } even though the chat title never changed: Bitrix24 answered such a call with a false success. The response gave no way to tell it apart from a real rename, so an integrator treated the operation as done. The caller could not even read that chat.

After

Membership is now checked before the rename. If the caller is not a member, the response is 404 CHAT_NOT_FOUND_OR_NO_ACCESS — the same as for any other non-participant — and no rename is attempted. The false success is gone. A rename by a member who has the rights works exactly as before.

NEW-0720-3: streaming chat-completions now ends a stalled upstream response with an explicit error

When a streaming call (POST /v1/chat/completions with stream: true) receives headers but the upstream model then goes silent mid-response and sends no new data for longer than the idle window, the proxy now terminates the call and emits a terminal error frame before data: [DONE]:

data: {"error":{"code":"stream_idle_timeout","type":"server_error","retryable":true,"retryAfter":<seconds>}}

Previously such a call hung indefinitely (the agent stayed in a "receiving stream response" state). The error is retryable — read the stream through to [DONE] and retry, honoring retryAfter. Normal (non-stalled) streams and reasoning models that continuously emit thinking tokens are unaffected.

NEW-0720-4: tasks: new timeSpentInLogs field

The timeSpentInLogs field (actual tracked time in seconds, summed from the time-tracking log) is now declared in the tasks schema — available in select, filter, and sort on GET /v1/tasks and POST /v1/tasks/search, and present in GET /v1/tasks/fields.

Previously the field was returned only when select contained BOTH spellings at once (timeSpentInLogs and TIME_SPENT_IN_LOGS); either one alone now works. The field is read-only — recorded via the time-tracking endpoint, not via a task update.

FIX-0720-5: GET /v1/files/:id?include=folder now returns the folder

Before

GET /v1/files/:id with ?include=folder answered 200 but without the _included block, even though GET /v1/files/fields advertised folder as includable — the include silently did nothing.

After

?include=folder attaches the folder under _included.folder, as /fields promises.

Impact on integrators

No action required. Clients reading _included.folder now get the object instead of nothing.

FIX-0720-6: /v1/me: infra block now accurate for server cap, provider id, and breakdown

Before

GET /v1/me returned infra.limits.max: 3 regardless of the enforced per-key server cap; infra.providers could return an internal provider id (disagreeing with GET /v1/infra/providers) with possible duplicates; infra.limits.breakdown counted managed-bot VMs under direct instead of bots.

After

infra.limits.max reflects the enforced per-key server cap; infra.providers returns the public provider id, consistent with GET /v1/infra/providers, with duplicates removed; infra.limits.breakdown counts bot VMs under bots. No integration change is needed — the values are simply correct now.

FIX-0720-7: deal-categories: unknown filter fields are rejected, sort by id respects direction

Before

GET /v1/deal-categories with a filter on an unknown field silently returned the WHOLE pipeline table with 200 — Bitrix24 ignores unknown filter keys of the legacy method and returns the entire list. And the sort ?sort=id&order=desc ignored the direction, always returning the same order.

After

A filter on an unknown or unsupported field (as well as operator prefixes >/>=/!/… and operator objects) is rejected before the Bitrix24 call with 400 UNSUPPORTED_FILTER; the message lists the filterable fields (id, name, sort). Exact-match and $in filters on those fields work as before. The sort ?sort=id now correctly respects asc/desc.

FIX-0720-8: openline-configs: unrecognised fields in a write body no longer vanish silently

Before

POST /v1/openline-configs (and PATCH) silently ignored unrecognised body fields — Bitrix24 drops unknown keys of the imopenlines.config.* method. A body of only unknown fields then created a configuration with default values and returned 200.

After

If the body has NO known field, the request is rejected with 400 VALIDATION_ERROR before the Bitrix24 call, and the message lists the unrecognised fields. If a known field is present but some fields are unrecognised, the write proceeds as before, and the response carries meta.warnings listing the ignored fields (previously they disappeared without a trace). Read-only fields (id, queue, dateCreate and others) in a write body are now rejected with 400 READONLY_FIELD — previously they passed as "known" and could lead to a configuration created with default values.

FIX-0720-9: GET /v1/{entity}/fields signals partial metadata on a Bitrix24 failure

Before

When the dynamic field fetch to Bitrix24 (*.fields) failed (rate limit, QUERY_LIMIT_EXCEEDED, queue timeout), the endpoint silently returned 200 with only the static schema fields — many of which have no human-readable label. The response looked complete, so a client could not tell it apart from a correct one and built non-deterministic field mappings.

After

On a field-fetch failure the response is still 200 with the static fields, but now carries meta.warnings: [{ "code": "fields_partial", "message": "..." }] so the client can detect the incomplete set and retry. The warning is an object { code, message } — the same channel and shape as meta.warnings on list/search, so a single warning.code parser works across every endpoint. The failure is now also logged on the Vibe side.

2026-07-19

BC-0719-1: Cowork/Code: tier lineup renamed — Free / Pro / Max / Ultra, Ultra price reduced

Old format supported until: 18.07.2026

Before

The tier field in GET /v1/cowork/state and GET /v1/cowork/me responses (as well as recommendation.upgrade.nextTier and the tiers[] catalog) took the values FREE, START, PRO, MAX. The MAX tier (×20) cost 40,000 Ꝟ/mo.

After

The lineup is renamed with a shift: STARTPRO, PROMAX, MAXULTRA; the value set is now FREE, PRO, MAX, ULTRA. Tier multipliers are unchanged: PRO ×1 (base), MAX ×5, ULTRA ×20; FREE is 5% of PRO. The ULTRA price (former MAX, ×20 volume) dropped from 40,000 to 20,000 Ꝟ/mo. Window volumes were recalibrated against real usage: the five-hour window doubled on every tier while monthly volumes were reduced; limits of an already-paid period do not change — the new values apply from the next renewal. The rename applies atomically at release time: the START value is no longer returned, and the ULTRA value was added. Clients branching on the string values of tier / nextTier must update their mapping and mind the meaning shift (PRO is now the base tier, MAX the middle one); clients that render the server-provided multiplier / feeVibes as-is keep working unchanged.

FIX-0719-2: Cowork/Code tier prices on the international version now use the dollar scale

Before

On the international version the tier catalog in GET /v1/cowork/state returned prices on the Russian-version scale: feeVibes 2000 / 10000 / 20000 for Pro / Max / Ultra. At 1 Vibe credit = 1 US dollar this read as 2000–20000 dollars per month.

After

The tier catalog on the international version returns the dollar grid: Pro — feeVibes: 20, Max — 100, Ultra — 200 per month; the three quota windows are scaled consistently, so tier capacity in requests is unchanged. The Russian version is not affected.

Impact on integrators

If your client reads tiers[].feeVibes from GET /v1/cowork/state on the international version, the values dropped ×100 and now match the price actually charged on activation. No code changes are required.

2026-07-18

FIX-0718-1: deploy reuses the application's server instead of creating a duplicate

Before

POST /v1/infra/servers with source always created a new server even when the application that owns the key already had one — a second, idle server was billed. And POST /v1/infra/servers/:id/deploy returned WRONG_KEY when the calling key differed from the one that created the server (for example, an application has both a personal key and an authorization key).

After

When the calling key belongs to an application that already has a live server, POST /v1/infra/servers returns that server with reused: true instead of creating a new one. If the same request carries source and the reused server is a galaxy app (kind: "GALAXY_APP") that has no running container yet (never deployed, or its previous deploy failed), the source is deployed to its own server right away (the response carries reused: true and deploying: true, and the server's status is provisioning while the build runs) — just like an ordinary create with source: poll GET /v1/infra/servers/:id until status is running, no second deploy call is needed. If the galaxy app is already serving, the response carries reused: true and next: "deploy" — deploy your source with a separate POST /v1/infra/servers/:id/deploy call (so the live container is untouched while the build runs). For a plain server, or a request without source, the response carries reused: true and next: "deploy" — deploy your source with a separate POST /v1/infra/servers/:id/deploy call. POST /v1/infra/servers/:id/deploy now accepts any key of the same application and deploys to its server.

Integrator impact

No changes required. Duplicate servers are no longer created. Previously a one-shot create with source against an application's existing server returned next: "deploy" and dropped the supplied source — now the source is deployed right away. Both a deploy and a status read (GET /v1/infra/servers/:id) of the application's server work under any of the application's keys, regardless of which one you call the API with.

FIX-0718-2: DELETE /lock now releases a stuck lock even on a deleted server

Before

DELETE /v1/infra/servers/:id/lock returned 404 NOT_FOUND when the server had been deleted — even though the operation lock remained in the platform's memory and kept the server busy. So the "previous server deleted, lock stuck, next deploy fails with EXEC_BUSY" scenario had no way out: such a lock could not be released via the API.

After

DELETE /lock releases a stuck lock even on a deleted server — as long as it still belongs to your API key (ownership remains the only check; the lock holds no data and no cloud resources). A successful call returns 200 with data.released: true. 404 NOT_FOUND now means only "the server does not exist or belongs to another key".

NEW-0718-3: connector app-install error codes are now also possible on cloud portals (phased rollout)

POST /v1/apps on a cloud portal can now also install the application through the vibecodeconnector module and therefore return the same connector error codes that were previously only possible on self-hosted portals: 403 CONNECTOR_APP_INSTALL_FORBIDDEN (the Bitrix24 portal administrator forbade the user from installing applications), 409 CONNECTOR_MODULE_NOT_INSTALLED (the vibecodeconnector module is not installed on the portal), and 502 CONNECTOR_APP_INSTALL_FAILED (other install failures). The change is additive: the successful-install response is unchanged, and the rollout is phased — on most cloud portals the install path is unchanged for now. Clients that already handle these codes on self-hosted portals need no change; clients that did not should add handling.

2026-07-17

FIX-0717-1: deploying to a sleeping galaxy host now answers before client timeouts

Before

POST /v1/infra/servers/:id/deploy on a sleeping shared host held the connection open for up to ~6.5 minutes while the host woke. HTTP clients with a typical response-headers timeout (~300 seconds — the Node fetch default) dropped the connection before the platform answered: the deploy surfaced as an opaque fetch failed network error with no error code and no recovery steps. A failed wake also put the host back to sleep, so every retry restarted the host boot from zero.

After

The platform wakes the host in the background and waits for it to connect for at most ~4 minutes. If the host connects in time, the deploy completes in one call as before. If not, the platform immediately returns a retryable 502 GALAXY_HOST_UNREACHABLE with a hint (re-send the same request in 1-2 minutes, do not delete the slot), and the host keeps booting in the background — a re-sent deploy joins the boot already in progress instead of restarting it. deployment.galaxyApp._rules and deployment.standalone._rules (GET /v1/me) now recommend an HTTP client timeout of at least 690 seconds — strictly above the platform's 660-second window.

Impact on integrators

No request changes are required. If a deploy to a sleeping galaxy host previously ended for you in a network error with no platform response, you will now get either a success or a 502 with retry instructions.

NEW-0717-2: server user search explains an empty list

The GET /v1/infra/servers/:id/b24-users endpoint now returns an additional hint field when the list is empty because there is no Bitrix24 access — the app is not yet authorized on the account, or the key was revoked. Existing calls are unaffected: the field is additive and absent on a successful response.

NEW-0717-3: the catalog field references now report nullable fields

The GET /v1/catalog-prices/fields, GET /v1/catalog-sections/fields and GET /v1/catalog-products/fields references now add "nullable": true to fields that can return null: for prices these are quantityFrom, quantityTo and extraId, for sections — iblockSectionId, xmlId, code and description, for products — iblockSectionId, code, weight, purchasingPrice, purchasingCurrency, quantity and quantityReserved. The same flag arrives in data.entities[].fieldsDetailed of the GET /v1/guide response, which an OAuth-application key reads without a session, and in the machine-readable GET /v1/openapi.json spec such fields are described with a union type like ["number", "null"]. A client building a typed model from the reference now gets the correct nullability and no longer breaks on the first null. The field set, the types and the response values are unchanged.

NEW-0717-4: EXEC_BUSY tells you when to retry

The 409 EXEC_BUSY response (another operation holds the server lock) on POST /v1/infra/servers/:id/exec and POST /v1/infra/servers/:id/deploy now carries a retry hint: the Retry-After response header (in seconds) plus two new error-body fields — retryable: true and retryAfter (in seconds). The retryAfter value is a short poll interval (keep retrying with it until the lock clears), not the full time until the lock auto-expires; the full upper bound stays in error.hint.autoExpiresInSeconds. The change is additive: the code, message, and hint are unchanged, so clients matching on error.code keep working without changes.

FIX-0717-5: deploying a galaxy app with an oversized archive returns 413, not "host unreachable"

Before

POST /v1/infra/servers/:id/deploy with a source.content larger than the upload limit returned 502 GALAXY_HOST_UNREACHABLE — a message that blamed an unreachable host and advised retrying "once it reconnects". The host was fully reachable, and re-sending the same archive failed identically: the integrator was stuck in an endless loop of useless retries.

After

The same case now returns 413 GALAXY_UPLOAD_TOO_LARGE with a structured error.hint. The cause is deterministic (the archive is too large), not transient, so re-sending it unchanged will not help. The hint advises shrinking the archive — exclude node_modules, .git and build artifacts (the platform installs dependencies on the host). A galaxy application accepts the inline source.content only (source.url is rejected with 400 GALAXY_DEPLOY_CONTENT_ONLY), so shrinking the archive is the only recovery. A separate adjacent case: a request body exceeding the platform's hard outer limit (500 MB on the base64 body ≈ ~375 MB binary archive) is now rejected at the edge with a coded 413 PAYLOAD_TOO_LARGE (on the Vibe-REST /v1/ routes — deploy/upload/create; OpenAI-compatible AI routes return their error in their own envelope) instead of raw HTML — previously the client got an undecodable response.

Impact on integrators

No change is required: successful deploys are unaffected. Clients that branched on the error code for this failure now see an honest 413 GALAXY_UPLOAD_TOO_LARGE instead of the misleading 502 GALAXY_HOST_UNREACHABLE — the latter remains for a genuine tunnel drop during the build.

NEW-0717-6: displayName and description on deploy set the Bitrix24 catalog card

POST /v1/infra/servers/:id/deploy accepted two new optional body fields — displayName and description. POST /v1/infra/servers (server creation) accepted an optional description. The values become the app's name and description on the Bitrix24 catalog card. If a deploy omits displayName and description, the response includes a warnings: string[] entry nudging the client to set them; the one-shot galaxy create-and-deploy (a POST /v1/infra/servers body carrying source) likewise returns warnings in its 201 response when the fields are absent. Backward-compatible — requests without the new fields keep working as before.

FIX-0717-7: galaxy deploy returns 503 on transient database overload

Before

On a brief database connection-pool exhaustion during a galaxy app deploy, POST /v1/infra/servers/:id/deploy returned a generic 502 GALAXY_APP_DEPLOY_FAILED — the same code as a real build failure. A client could not tell a transient overload from a terminal error and often treated the response as final.

After

A transient database overload is now shed as 503 POOL_EXHAUSTED with a Retry-After header (seconds to wait before retrying). A genuine build failure stays 502 GALAXY_APP_DEPLOY_FAILED.

Integrator impact

No action required. If your client retries, a 503 now carries an explicit backoff signal via Retry-After instead of an opaque 502.

FIX-0717-8: filtering and sorting of the task comment list work on both cards

Before

A GET /v1/tasks/:taskId/comments request with a filter parameter or with a sort other than ID on a Bitrix24 account with the new task card returned 200 and an empty list, even when the task did have comments. No error arrived, so there was no way to tell "nothing matched the filter" from "the filter did not work".

After

Such a request returns the matching comments. Filtering and sorting work by the fields ID, AUTHOR_ID and POST_DATE, and in a filter the field name may carry a !, >, >=, < or <= prefix. Filtering by AUTHOR_NAME and sorting by AUTHOR_NAME or AUTHOR_EMAIL on the new card answer with 400 and the code UNSUPPORTED_FILTER_FIELD or UNSUPPORTED_SORT_FIELD and point at AUTHOR_ID, on the old card those fields are still accepted. An offset parameter is added — it is honoured on the new card for a request with filter or a sort other than ID, and ignored on the other read paths. The INVALID_FILTER code now also arrives when filter is a scalar or an empty array instead of an object (0, false, "", []), the value of the field ID or AUTHOR_ID is not a number, the value of POST_DATE does not parse as a date, or a field value is an object or an array instead of a scalar. A truncated field with the value true is added to meta — the scan window of the history was exhausted, and some comments stayed beyond its edge.

Impact on integrators

Nothing to change: a request that used to hand back an empty list starts handing back data. Mind three edges. First — filtering by AUTHOR_NAME and sorting by AUTHOR_NAME or AUTHOR_EMAIL on a Bitrix24 account with the new card now answer with 400 instead of an empty 200, move such a request to AUTHOR_ID, an employee identifier by name comes from GET /v1/users. Second — meta.total for a filtered request against the new card counts the matching comments within the scanned window rather than across the whole task history, and with meta.truncated: true it is an incomplete number. Third — the value of POST_DATE on the new card is compared against createdAt in UTC, so a result on a day boundary may differ from the selection on the old card. On Bitrix24 accounts with the old card the behaviour is unchanged.

2026-07-16

BC-0716-1: json_object on a reasoning model recovers JSON; the 422 body is refined

Old format supported until: 15.01.2027

Before

A POST /v1/chat/completions request with a json_object response_format on a reasoning model (e.g. bitrix/bitrixgpt-5.5-agent) consistently returned 422 structured_output_truncated, even when the model finished on its own (finish_reason: "stop") and placed a complete valid JSON into the service reasoning_content channel — the answer was lost. Every such error body carried error.suggestedMaxTokens and error.param, and the text claimed "finish_reason=length" regardless of the real stop reason.

After

If a model on json_object finished on its own and valid JSON sits in reasoning_content, the platform recovers it and returns 200 with that JSON in content (in streaming mode — as a content chunk before the terminal finish_reason chunk). The 422 body is now truthful: error.suggestedMaxTokens and error.param are present only on a genuine truncation (finish_reason: "length"); on any other stop reason (stop, etc.) those fields are omitted and the text names the actual finish_reason. json_schema behaviour is unchanged — there the strict schema is enforced model-side.

What to do

Nothing, if you handle 422 by error.code. If your code unconditionally reads error.suggestedMaxTokens or error.param on a structured_output_truncated error, make the read optional: those fields are now absent when finish_reason !== "length". Streaming clients using response_format should accumulate content across all deltas up to data: [DONE].

FIX-0716-2: env sent as a file in a multipart deploy is no longer silently ignored

Before

In multipart/form-data for POST /v1/infra/servers/:id/deploy, an env field sent as a file or Blob was silently ignored — the deploy reported success but the app started without environment variables.

After

Such a request returns 400 with code VALIDATION_ERROR and a hint to send env as a text field containing a JSON string.

Impact on integrators

The correct approach (a text env field with a value like {"KEY":"value"}) is unaffected. Anyone who sent env as a file or Blob now gets an explicit error instead of a false success.

FIX-0716-3: large source.content deploys no longer fail with Gateway HTTP 413

Before

On some accounts POST /v1/infra/servers/:id/deploy with source.content (a base64 archive) failed at the download step with { "code": "DEPLOY_FAILED", "message": "Gateway HTTP 413", "step": "download" } whenever the base64 body exceeded ~1 MB (about 768 KB of raw tar.gz) — despite the documented 500 MB limit. The workaround was uploading via source.url.

After

The 500 MB inline-upload limit (source.content and multipart) applies on all accounts. source.url keeps working as before.

BC-0716-4: POST /v1/infra/servers rejects unknown body fields

Old format supported until: 15.01.2027

Before

An unknown field in the request body was silently ignored. A request with deployMode: "STANDALONE" (a non-existent field) returned 201 and created a galaxy app instead of the expected dedicated server — the real field is placement: "dedicated".

After

POST /v1/infra/servers rejects a body that carries an unknown field with 400 UNKNOWN_PARAM. details.unknownFields lists the extra fields, details.suggestions proposes the correct name (deployModeplacement), and details.validParams is the full list of accepted fields.

What integrators should do

Remove fields that are not in the create parameter list, or fix the typo using details.suggestions. The placement model is set via the placement field (auto by default, dedicated for a dedicated virtual machine).

NEW-0716-5: /v1/sites/fields now describes the allowed values of the type field

GET /v1/sites/fields now returns the allowed values of the type field in type.enum with labels: PAGE (landing), STORE (online store), KNOWLEDGE (Knowledge Base 2.0), plus VIBE (a site from the constructor) and SMN (a link with the "Site Management" module). The VIBE and SMN values appear in responses only and are read-only — a site of that type cannot be created through the API.

NEW-0716-6: Task favorite and pin without edit permission

Four new endpoints for per-user task actions that Bitrix24 allows with read access only (not edit): POST /v1/tasks/:taskId/favorite adds the task to favorites, DELETE /v1/tasks/:taskId/favorite removes it, POST /v1/tasks/:taskId/pin pins the task in the current user's task list, and DELETE /v1/tasks/:taskId/pin unpins it. Previously the only way to change a task was PATCH /v1/tasks/:id, which requires edit permission and returned "No access to edit the task", so an allowed per-user action was unreachable.

NEW-0716-7: preemptible-plan advisory in scheduled-wake responses

The POST /v1/infra/servers/:id/wake-schedules and PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId responses now also carry two additional top-level fields: preemptibleAdvisoryCode and preemptibleAdvisory. When the server runs on a preemptible plan, preemptibleAdvisoryCode is "PREEMPTIBLE_BEST_EFFORT" and preemptibleAdvisory is a short English explanation of the same fact: waking such a server by the window's moment is not guaranteed, and the window may be skipped when no free capacity is available. For a server on a non-preemptible plan, both fields are null. The advisory does not block window creation or updates — it follows the same non-blocking pattern already used by the tzWarning/tzWarningCode fields. Existing integrations that don't read the new fields keep working unchanged.

2026-07-14

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-portal

Before

The limits (/v1/search 60/min, /v1/research 20/min, /v1/batch 30/min) were effectively keyed by source IP, not by portal. A portal using multiple API keys (or several portals 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 portal: all API keys of one portal 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 portal spread /v1/search / /v1/research / /v1/batch traffic across several API keys, the effective ceiling is now the single portal 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 iblockId422 ("Required fields: iblockId" — B24 mandates it on every update), and PATCH with iblockId400 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-portal) entity type:

  • The path entityTypeId (/v1/items/:entityTypeId/..., /v1/categories/:entityTypeId/... — CRUD and /aggregate): GET /v1/items/1058abc truncated to 1058, 1e31, 1.51.
  • 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 :id400 INVALID_PARAMS before any Bitrix24 call (id 0 — the main deal funnel 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 are localized to the segment language. 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-portal; `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 portal: all API keys of one portal 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 portal.
  • 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 on the portal — on portals 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-productsiblockId); a missing required list-param → 400 MISSING_REQUIRED_PARAMS (e.g. calendar-eventstype,ownerId; humanresources-nodestype). 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 synchronised 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 portals 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 portals, 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.

2026-07-13

FIX-0713-1: OAuth app key platform scopes now sync on create and edit

Before

A key issued together with an OAuth app via POST /v1/apps did not receive the platform scopes (vibe:infra, vibe:ai, vibe:search, vibe:storage) that a cabinet-created key gets. As a result POST /v1/infra/servers under such a key returned 403 INFRA_SCOPE_REQUIRED. Adding vibe:infra to the app's scopes via PATCH /v1/apps/:id changed only the app, not the paired key — so it had no effect on access.

After

The paired key created via POST /v1/apps now receives the same default platform scopes as a cabinet key. A vibe:* scope change via PATCH /v1/apps/:id (both add AND remove) now propagates to the app's active keys. A read-only (READONLY) key is rejected with 403 WRITE_BLOCKED_READONLY_KEY on three write operations: server creation (POST /v1/infra/servers), app scope change (PATCH /v1/apps/:id), and READWRITE key issuance (POST /v1/apps with mode: "READWRITE").

Impact on integrators

Apps created via the API can now manage infrastructure without re-creation. A key whose app already declares vibe:infra while the key itself lacks it (the old divergence) is fixed with one edit: remove vibe:infra from the app's scopes and add it back via PATCH /v1/apps/:id — the second edit syncs the key; or re-create the app. A READONLY key can still create a READONLY app (mode: "READONLY").

FIX-0713-2: product-sections: unsupported filters return 400 instead of the whole table

Before

Operators (>, <, !, %, $ne, $contains, $nin) and non-exact-match fields (sort, unknown) in the filter of GET /v1/product-sections and POST /v1/product-sections/search were silently ignored — the code 200 and the whole list were returned unfiltered.

After

Such filters are rejected with 400 UNSUPPORTED_FILTER. Filter by exact match or $in on id, name, xmlId, code, catalogId, sectionId. Sorting (order/sort) is unchanged.

Impact on integrators

Calls with operators or filter[sort] that previously returned 200 with unfiltered data now return 400 — switch to exact match or $in.

FIX-0713-3: the reason for a failed first server provisioning is now visible

Before

If a server failed to come up on its first creation (a preemptible plan evicted, no free capacity), it silently ended up in sleeping status with no explanation. A client polling GET /v1/infra/servers/:id saw sleeping and could not tell why the deploy was blocked.

After

Such a server now moves to status: "error" with a populated provisionError (a human-readable reason) and a new provisionErrorCode field — a machine-readable failure category (PREEMPTIBLE_EVICTION / PROVISION_TIMEOUT / NO_CAPACITY / GENERIC). The provisionErrorCode field is added to GET /v1/infra/servers/:id and GET /v1/infra/servers next to provisionError (additive, null when there were no errors).

Integrator impact

No action needed: error is an already-existing status. Recover such a server via POST /v1/infra/servers/:id/start or /repair (not /wake: on error status it returns 422; the availableActions field in the response points to the available action).

NEW-0713-4: POST and PATCH /v1/tasks/:taskId/time accept createdDate

An optional createdDate field is now forwarded on POST /v1/tasks/:taskId/time and PATCH /v1/tasks/:taskId/time/:itemId — the time entry lands on the given date instead of the current moment (backfilling last week's tracks). ISO 8601 with offset, bare ISO, and YYYY-MM-DD are all accepted and passed to CREATED_DATE verbatim. When the field is omitted the behaviour is unchanged — the date equals the creation moment.

FIX-0713-5: meta.hasMore no longer stays stuck at true with filter + offset

Before

When paginating a filtered list (for example GET /v1/deals with filter and offset), meta.hasMore stayed true at every offset — even well past meta.total. A while (meta.hasMore) { offset += limit } loop ran forever.

After

When Bitrix24 ignores an offset beyond the filtered set and returns the whole set, meta.hasMore is derived from the request window (offset + limit < meta.total) instead of being forced to true. At offset=0 the response is unchanged; once the window passes total, hasMore becomes false. Applies to list and POST /search across all entities.

FIX-0713-6: GET /v1/lists and /v1/lists/:iblockId/elements honour offset

Before

GET /v1/lists/:iblockId/elements and GET /v1/lists silently ignored offset?limit=50&offset=50 returned the same first page, and a client paginating by offset never reached rows 51 and beyond.

After

offset is mapped to start, the native pagination param for these methods, so offset-based paging works. An explicit start keeps priority when both are supplied.

NEW-0713-7: New preserveEnv flag keeps .env across cleanDeploy

The POST /v1/infra/servers/:id/deploy body gained an optional boolean preserveEnv (default false). When cleanDeploy: true (which wipes the app directory including the .env file) and preserveEnv: true, the existing .env is read before the wipe and restored afterwards unless this request also passes env (a supplied env wins). Without the flag a re-deploy with cleanDeploy could boot the app with no environment variables — on its default port.

NEW-0713-8: failed galaxy app builds now carry a `buildHint`

Before

A galaxy app build failure returned only GALAXY_APP_BUILD_FAILED (and GALAXY_APP_START_FAILED) with a buildLog tail — you had to read the log by hand to find the cause. GET /v1/infra/servers/:id for an ERROR app returned a short provisionError but no ready recommendation.

After

The response now carries the parsed cause. The error body of POST /v1/infra/servers/:id/deploy (502) additionally includes error.category (a machine category such as MODULE_NOT_FOUND, INSTALL_AUTH, RESOURCE) and error.buildHint — a localized recommendation line naming the concrete next step, whenever the failure is classifiable. GET /v1/infra/servers/:id for an ERROR app adds the same data.buildHint field. The fields are additive: when the cause is unrecognized they are absent (buildHint is null), and provisionError/buildLog keep arriving as before — existing requests are unaffected.

FIX-0713-9: concurrent identical source saves no longer mint a duplicate version

Before

Two simultaneous saves of identical bytes to the same server (POST /v1/infra/servers/:id/sources, and the auto-save on deploy) could, under rare timing, create two byte-identical versions instead of one — content deduplication was best-effort.

After

Deduplication is deterministic: identical bytes submitted concurrently always converge on a single version. On POST /v1/infra/servers/:id/sources both responses return the same versionId and the losing request gets deduplicated: true; the deploy auto-save converges on that same single version (the deploy response shape is unchanged).

2026-07-12

NEW-0712-1: The AI quota exhaustion response now points to the top-up path

The 402 ai_quota_exhausted error with reason: wallet_empty now additionally returns the hint and topupUrl fields. hint is a short English message: the monthly AI quota and the account balance are exhausted, and an account admin can top up the balance to resume. topupUrl is a link the admin opens to top up. The fields are additive: the existing response fields (reason, resetAt) are unchanged, and the reason: breaker and reason: wallet_off branches do not carry them. This lets an agent client relay the top-up path to the human. The error is returned by model calls, see POST /v1/chat/completions.

FIX-0712-2: `sleep-now` on a server with a long-past computed wake now sleeps honestly

Before

For a server with an enabled wake schedule, POST /v1/infra/servers/:id/sleep-now could return { data: { slept: false, reason: "WAKE_IMMINENT" } } forever when the denormalized "next wake" was left far in the past (the server was woken outside the scheduler and the stamp was never recomputed). The server never slept and kept its plan running around the clock.

After

A stamp older than a dynamic window (grace = margin + typical lead) is treated as stale rather than "imminent": sleep-now sleeps the server honestly and answers { success: true }, after which the scheduler quietly rolls the anchor forward to the future window without waking it. WAKE_IMMINENT stays a normal response only for a genuinely near wake.

2026-07-11

FIX-0711-1: galaxies: GALAXY_HOST_UNREACHABLE is now actionable — structured hint and a precise provisionError

Before

When a galaxy host was temporarily unreachable, POST /v1/infra/servers/:id/deploy answered with a bare 502 GALAXY_HOST_UNREACHABLE, and a slot created in one call via POST /v1/infra/servers with source moved to an error status with the text "Deploy failed unexpectedly — please retry; details are in the server logs". Neither the cause nor the recovery path was reported — clients deleted the slot and created a new one, which does not help: the fresh slot lands on the same host.

After

The 502 GALAXY_HOST_UNREACHABLE response — on deploy, exec and delete (DELETE /v1/infra/servers/:id) — carries a structured error.hint: the condition is transient, the slot and its data are intact, retry the same request in 1–2 minutes, do not delete the slot. On the create-with-source path, provisionError now contains the real cause ("Galaxy host … became unreachable during build …") and the same advice to re-send the deploy to the existing slot. Additionally: when several servers work under one OAuth application, a saved source version is no longer lost to a version-numbering conflict — neither on the deploy auto-save nor on an explicit save via POST /v1/infra/servers/:id/sources.

Impact on integrators

The change is additive: response codes and statuses did not change; the error.hint field was added and the provisionError text became precise. No client updates required; AI agents should read hint.recovery — it states exactly what to do.

NEW-0711-2: distinct error code when the Vibecode Connector module is not installed on the portal

Issuing an application key through the connector module (POST /v1/apps) now returns 409 with code CONNECTOR_MODULE_NOT_INSTALLED and a clear "install the module" message when the portal has no vibecodeconnector module, instead of the previous opaque 502 CONNECTOR_APP_INSTALL_FAILED. This is a legitimate, permanent state (especially for a self-hosted portal), not a transient failure — retrying will not help, the module must be installed on the portal. Other issuance error codes are unchanged.

FIX-0711-3: GET /v1/ai/quota pacing can now be populated by the platform by default (gradual rollout)

The platform can now enable pacing (AI-quota day/week smoothing) by default for an account — no admin action required. The rollout is staged (pilot accounts → all accounts): until an account falls under the platform default-on, the data.pacing field in the GET /v1/ai/quota response stays null, same as before. Once an account is under default-on: mode: "ignore" is an informational mode, and active: false means window limits do not reject requests (a 429 ai_pacing_limited rejection is impossible). The response shape is unchanged; integrators who already treat data.pacing as an optional field need no changes.

2026-07-10

NEW-0710-1: wake-schedule window management (wake-schedules)

New CRUD contract on Black Hole servers: GET/POST /v1/infra/servers/:id/wake-schedules, PATCH/DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId. Lets you declare one or more recurring wake windows (cronExpr + a required IANA timezone, optional label/lead/enabled) — the platform wakes a sleeping server ahead of the window, and the app's own in-VM cron runs the task from there.

Rolling out gradually and not yet available on every portal — until enabled on a given portal, the call returns 403 with code WAKE_SCHEDULE_DISABLED. Available only for BLACKHOLE-mode servers (otherwise 400 BLACKHOLE_ONLY) and not yet supported for galaxies (400 GALAXY_NOT_SUPPORTED on both the host and a nested app — coming later). The minimum cadence between occurrences and the per-server window cap (50) are platform-configured; violating either returns 400 CADENCE_TOO_LOW and 403 WAKE_SCHEDULE_LIMIT respectively. The create/update response additionally carries a tzWarning field — a heads-up that the VM's in-VM timezone may have drifted from the window's timezone if the server hasn't been redeployed since. PATCH replaces the whole window (PUT semantics): omitted optional fields reset to their defaults — enabledtrue, label/lead→null. Send the full window object on update.

Affected endpoints: GET|POST /v1/infra/servers/:id/wake-schedules, PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId.

NEW-0710-2: machine-readable tz-warning code in wake-schedules responses (`tzWarningCode`)

The wake-schedule create/update response (POST/PATCH /v1/infra/servers/:id/wake-schedules[/:scheduleId]) now additionally carries a tzWarningCode field alongside the existing text tzWarning"SINGLE_ZONE" / "MULTI_ZONE" / null (when the server ends up with zero enabled windows after the mutation). It's a machine-readable companion to the same advisory, letting clients localize the message themselves instead of rendering the raw English tzWarning text. The field is additive — tzWarning is unchanged and stays for backward compatibility.

Affected endpoints: POST|PATCH /v1/infra/servers/:id/wake-schedules[/:scheduleId].

FIX-0710-3: wake-schedules: schedules rejected on always-on servers

POST/PATCH /v1/infra/servers/:id/wake-schedules now reject creating or updating a wake window on an always-on (24/7) server — the call returns 400 with code ALWAYS_ON_CONFLICT. Such a server runs on a non-preemptible plan and never auto-sleeps, so a wake schedule would silently break the paid always-online guarantee. Ordinary sleeping Black Hole servers and preemptible agents/bots are unaffected. Turn off always-on to declare wake windows.

Affected endpoints: POST /v1/infra/servers/:id/wake-schedules, PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId.

FIX-0710-4: wake-schedules can now be declared on nested galaxy apps

Before

POST/PATCH /v1/infra/servers/:id/wake-schedules returned 400 GALAXY_NOT_SUPPORTED for any galaxy-family server — both the host itself and nested apps (kind=GALAXY_APP).

After

Nested galaxy apps (kind=GALAXY_APP) are now accepted — you can declare a wake window on a specific app, and the platform wakes it (and the host, if needed) ahead of the window. The galaxy host itself still returns 400 GALAXY_NOT_SUPPORTED — schedule the individual apps, not the host.

Affected endpoints: POST|GET /v1/infra/servers/:id/wake-schedules, PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId.

FIX-0710-5: sleep-now declines to sleep when a scheduled wake is imminent

Before

POST /v1/infra/servers/:id/sleep-now always put the server to sleep immediately, even when the next scheduled wake was a minute away — the server woke straight back up.

After

When the server has an enabled wake schedule and the next wake falls within the guard margin, the call does not sleep the server and responds 200 with { "success": true, "data": { "slept": false, "reason": "WAKE_IMMINENT" } }. Otherwise the server sleeps and the scheduler wakes it at the next window.

Impact on integrators

The data block with slept: false arrives only when the call declines to sleep the server — check for its presence if you rely on the server always sleeping after this call. On a successful sleep the response is just success: true. Existing calls to servers without a schedule are unchanged.

FIX-0710-6: non-streaming chat/completions and embeddings no longer abort at 360 seconds

Before

A non-streaming (stream:false) POST /v1/chat/completions (and POST /v1/embeddings) request whose generation took longer than ~2 minutes reliably aborted at ~360 seconds with {"code":"ai_provider_unavailable","message":"This operation was aborted"} — regardless of the client's own timeout. The doomed generation also burned triple the compute.

After

Such a request now runs within a single ~850-second budget (one attempt for the whole budget, no compute tripling). If generation still does not finish in budget, a meaningful 503 with code ai_provider_timeout is returned, with a localized userMessage and a hint (reduce the request size or use streaming mode stream:true) — without a Retry-After header (the timeout is not transient). A client disconnect now immediately cancels the provider-side generation. Streaming mode (stream:true) was never affected by this limit.

Before

GET /v1/requisite-presets/:presetId/fields ignored sort/order and any filter[...] — it always returned the full list in Bitrix24 order. GET /v1/requisite-links and POST /v1/requisite-links/search accepted only plain equality; comparison operators ($gte, >, etc.), sort/order, and unknown fields were silently dropped, so the whole table came back.

After

Both lists now honour sort/order (including the order[field]=asc|desc form) and filter. requisite-links supports comparison operators ($gt/$gte/$lt/$lte, $in/$nin, >=/> prefixes). An unknown filter or sort field now returns 400 (UNKNOWN_FILTER_FIELD / UNKNOWN_SORT_FIELD), a top-level logical operator ($or/$and) returns 400 INVALID_FILTER_OPERATOR, and filtering by entityId without entityTypeId returns 400 MISSING_ENTITY_TYPE_ID instead of a raw "Access denied".

Impact on integrators

Requests on documented fields keep working and now actually sort/filter. If you relied on an unknown parameter being silently ignored, it now returns 400 — drop the typo or use a field from the response.

FIX-0710-8: Bank details sorting by id honors the direction

Before

Listing bank details (GET /v1/bank-details) sorted by id descending (?sort=-id) returned records in ascending order — the sort direction was silently ignored.

After

?sort=-id (and ?sort=id) sorts by the identifier in the requested direction.

Integrator impact

No code change required — requests that relied on sorting by id now return the expected order.

NEW-0710-9: GET /v1/quotes/fields — ~26 quote fields declared with human-readable labels

The quotes entity schema now declares ~26 fields that Bitrix24 returned but that were undeclared: quoteNumber, updatedBy, lastActivityBy, lastActivityTime, content, terms, leadId, storageTypeId, storageElementIds, personTypeId, webformId, lastCommunicationTime, contactIds, contacts, locationId, taxValue, actualDate, mycompanyId, utmSource/utmMedium/utmCampaign/utmContent/utmTerm, lastCommunicationCallTime/lastCommunicationEmailTime/lastCommunicationImolTime/lastCommunicationWebformTime. They now appear in GET /v1/quotes/fields with readable labels (instead of service names like STORAGE_TYPE_ID/UTM_SOURCE), and their values are type-coerced (numbers, ISO dates) in list/get responses. Labels were also added to the previously label-less stageId, opened, closed.

FIX-0710-10: a PATCH with no writable field is rejected with an explicit error

Before

PATCH /v1/{entity}/:id with an empty body — or a body carrying no recognized writable field (for example because of a typo in a field name) — reached Bitrix24, which silently ignored the request and returned success. The wrapper then replied 200 with the unchanged object, so the client believed the edit had applied while nothing actually changed — a silent-drift risk, especially for AI agents. This affected all three update surfaces: single PATCH /v1/{entity}/:id, per-entity POST /v1/{entity}/batch and global POST /v1/batch.

After

An update with an empty body returns 400 EMPTY_UPDATE_BODY (a per-item error in batch calls) on all three surfaces, before Bitrix24 is called. For /v1/catalog-products/:id a strict check is added: a PATCH that carries no recognized writable field returns 400 NO_RECOGNIZED_UPDATE_FIELDS. A meaningful update always carries at least one field — send a recognized field (custom properties propertyNNN and UF_* fields are accepted too). Entities that already validated their fields behave as before.

Catalog products additionally expose in GET /v1/catalog-products/fields, and now accept for explicit select, filter and sort, the fields from the product-update contract — code, xmlId, sort, vatId, height, length, width, previewText, detailText and others (filtering by them previously returned 400 UNKNOWN_FILTER_FIELD). Filter and sort reliably by the indexable fields (code, xmlId, sort, vatId, dimensions); for the full-text ones (previewText, detailText) Bitrix24 may ignore the filter. List responses without an explicit select are unchanged.

FIX-0710-11: leads, companies, quotes — /fields date keys are now createdTime and updatedTime

Before

GET /v1/leads/fields, GET /v1/companies/fields and GET /v1/quotes/fields advertised createdAt and updatedAt, while list/get/search responses always returned createdTime and updatedTime — the value was never readable under the createdAt/updatedAt name. Filter and sort accepted the createdAt/updatedAt names.

After

These entities' /fields advertise the real keys createdTime and updatedTime — like contacts, invoices and items. The keys in the response body are the same (createdTime/updatedTime were always returned), but the value is now normalized to ISO-8601 in UTC (2026-04-15T07:00:00.000Z) — previously it arrived in the raw Bitrix24 form with the account offset (2026-04-15T08:00:00+01:00). Same instant, only the representation changes.

Impact on integrators

Read dates from createdTime and updatedTime (the keys did not change). If you compare the date string byte-for-byte or cache by it, account for the offset → Z shift (same instant). In filter and sort use createdTime/updatedTime; the former createdAt/updatedAt now return 400 UNKNOWN_FILTER_FIELD (filter) and 400 UNKNOWN_SORT_FIELD (sort). On write, createdAt/updatedAt are no longer rejected as read-only — they are ignored as unknown fields (like contacts/invoices/items); the creation/update timestamp still cannot be set.

FIX-0710-12: sleep settings: flipping to always-on is now rejected while wake windows are active

PATCH /v1/infra/servers/:id/sleep now rejects setting sleepAfterMinutes: null (always-on, 24/7) on a server that still has enabled wake-schedule windows — the call returns 400 with code ALWAYS_ON_CONFLICT. This is the reverse direction of an existing gate: creating a wake window on an always-on server was already rejected, and now the opposite transition is rejected symmetrically — otherwise the server would keep sleeping on schedule, silently breaking the paid always-online guarantee. Delete or disable the wake windows first, or keep a sleep timeout instead of "Never", to turn always-on on.

Affected endpoints: PATCH /v1/infra/servers/:id/sleep.

NEW-0710-13: placement.bind on a self-hosted Bitrix24 returns a clear SESSION_REQUIRES_ADMIN for a non-administrator

On a self-hosted (BOX) Bitrix24, binding a placement via the developer-key path requires the key's user to be an account administrator. Previously a non-administrator request returned an opaque 502 BITRIX_UNAVAILABLE.

Now POST /v1/placements/bind recognises the access-denied signal from Bitrix24 and returns 403 SESSION_REQUIRES_ADMIN with a hint: bind from an account-administrator account, or ask an administrator to grant those rights. The requirement is visible up front in GET /v1/me — the placements.bindPrerequisite block for self-hosted accounts now includes the SESSION_REQUIRES_ADMIN code.

FIX-0710-14: GET /v1/me capabilities now reflect read-only (READONLY) mode

Before

For a READONLY-mode key, GET /v1/me returned capabilities.managedBots.create, agents.create, servers.create and apps.* as available: true, even though every write is blocked with 403 WRITE_BLOCKED_READONLY_KEY. An agent saw "can create" and hit the rejection.

After

For a READONLY key these write capabilities are returned as available: false with reason: "WRITE_BLOCKED_READONLY_KEY" and a hint to switch the key to read+write. Read-only capabilities and the AI Router are unchanged. For READWRITE keys the response is unchanged.

FIX-0710-15: the feedback author can reply to their own ticket again without the vibe:feedback scope

Before

POST /v1/feedback/:id/comments rejected the ticket author with 403 FEEDBACK_SCOPE_REQUIRED when the key lacked the vibe:feedback scope — even though the author branch was documented. The author could not reply to their own AWAITING_USER ticket, and it stalled.

After

The author check now runs before the scope gate: the author replying with the same key that created the ticket reaches the author branch (authorType=USER, ball-court rule AWAITING_USER → NEEDS_REVIEW) even without the vibe:feedback scope. A key that is neither the author nor scoped still gets 403 FEEDBACK_SCOPE_REQUIRED.

NEW-0710-16: AI quota pacing: pacing field in the response and 429 ai_pacing_limited error

The GET /v1/ai/quota response gained a pacing field — the state of monthly AI quota pacing (peak smoothing via a daily and a weekly limit layered on top of the overall monthly limit; enabled by the portal admin from the /ai cabinet page). null when pacing is off at the platform level or not configured for the account; otherwise an object { mode, active, day, week }: mode is the enforcement behavior on a tripped limit (wallet/block/ignore), active signals whether tripping a window will reject a call right now (false in observation mode, and always false in ignore mode — windows are tracked as informational only, 429 is never returned), day and week are each { pctUsed, resetAt } as a percentage of that window's OWN limit. The response is still cached for 30 seconds, so pacing state can lag by that long.

When the daily or weekly limit trips, calls to POST /v1/chat/completions, POST /v1/embeddings, and POST /v1/audio/transcriptions can now return 429 with the body { success: false, error: { code: "ai_pacing_limited", type: "rate_limit_exceeded", message, reason, overageDenied, resetAt, retryAfter } } and a Retry-After header. reason names the tripped window (day_window or week_window); overageDenied names why paid overage was refused (wallet_empty, breaker, wallet_off), or is null under the hard-block mode. Retrying before Retry-After/resetAt will not help — the quota is not any more available in the meantime. Pacing is off by default — the platform enables it.

FIX-0710-17: GET /v1/pages, /v1/sites and POST /v1/{pages,sites}/search now honour offset

Before

Listing pages or sites with an offset (GET /v1/pages?offset=50, POST /v1/pages/search with offset) returned 422 BITRIX_ERROR "Unknown parameter: start". The first page (no offset) worked.

After

offset for pages and sites is handled correctly: the requested window [offset, offset+limit) is returned, with meta.total and meta.hasMore computed from the row count. No client change is needed — calls without offset behave as before.

NEW-0710-18: /fields of documents, catalogs, prices, telephony lines, basket items and leads gain richer metadata

GET /:entity/fields of several entities now carries more complete schema metadata. Documents (GET /v1/documents/fields), catalogs (GET /v1/catalogs/fields), catalog prices (GET /v1/catalog-prices/fields) and telephony lines now expose a human-readable label and description per field, returned in English.

For basket items (GET /v1/basket-items/fields) the fields weight, vatRate, measureCode, measureName, dimensions, productXmlId, catalogXmlId are marked with a nullable flag — they may come back empty. For leads (GET /v1/leads/fields) the same flag marks secondName, sourceDescription, comments, and the previously-undeclared fields originatorId, dateClosed, lastCommunicationTime and the tags utmSource/utmMedium/utmCampaign/utmContent/utmTerm are now declared — filter and sort work on them, and dateClosed is normalized to ISO-8601.

For landing sites the group-by dimensions are declared, so POST /v1/sites/aggregate with groupBy (type, active, deleted, lang, tplId, domainId, createdById, modifiedById) no longer answers Available: .. For documents aggregation is disabled (every numeric field is an identifier): POST /v1/documents/aggregate returns 404.

Existing calls keep working unchanged — this is additional field metadata.

NEW-0710-19: Idempotent server creation — Idempotency-Key header

POST /v1/infra/servers now accepts an optional Idempotency-Key header when creating a standalone server. A retry with the same key — for example after a lost response or a network drop — does not create a second server: it returns the same server the first request created, with status 201 and an Idempotent-Replayed: true response header. The key is a 1–255 character string from [A-Za-z0-9_.:-]; it is scoped to your API key.

On a replay the one-time SSH credentials (ssh.privateKey / ssh.password) are NOT re-exposed — they are null in the response body and a note field explains this. Keep the credentials from the first create response.

New error codes: 400 INVALID_IDEMPOTENCY_KEY (the key fails validation), 400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION (the key together with graduateFrom for a dedicated server is not supported), 409 IDEMPOTENCY_KEY_ALREADY_USED (the key was already used for a server that has since been deleted), 409 IDEMPOTENCY_CONCURRENT_RETRY (a concurrent request with the same key is still in progress — retry shortly).

The header applies to standalone servers only. On galaxy-placement portals a well-formed key is ignored without an error, and the retry protection does not extend to that path.

Additionally: the create response now returns the canonical server name (suffixed when a name collision is resolved) instead of the name from the request — these previously diverged on a same-name collision.

NEW-0710-20: localized message on OPEN-mode switch denials

Responses from PATCH /v1/infra/servers/:id/mode with codes OPEN_MODE_DISABLED (OPEN mode is disabled platform-wide) and OPEN_MODE_NOT_ALLOWED (OPEN mode is not allowed by portal policy) now additionally carry an error.userMessage field — a localized, human-readable message pointing to the Deploy API as the supported replacement for direct SSH. The field is additive: error.message (English technical string), error.code and the HTTP status are unchanged. It matches the shape of the existing error.userMessage on the OPEN_MODE_REQUIRES_COMMERCIAL denial. The userMessage text depends on the key owner's locale.

BC-0710-21: Open line config fields normalized to camelCase and described in /fields

Old format supported until: 10.01.2027

Before

GET /v1/openline-configs, GET /v1/openline-configs/{id} and POST /v1/openline-configs/search returned most configuration fields in the Bitrix24-native form — upper case with underscores (CRM_CREATE, WELCOME_MESSAGE, QUEUE_TIME, and others). The GET /v1/openline-configs/fields reference described only 6 fields, so the rest were invisible to programmatic discovery.

After

All configuration fields are normalized to a single camelCase form (crmCreate, welcomeMessage, queueTime, and so on), and /fields describes the full field set with human label and description values. Filtering and sorting by the new camelCase names work. On write (create/update) both cases are still accepted — existing calls that send upper-case names in the body keep working.

What integrators should do

Read response fields by their camelCase names: config.crmCreate instead of config.CRM_CREATE. The mapping is a direct transliteration from upper case to camelCase (WELCOME_BOT_IDwelcomeBotId, WORKTIME_TOworkTimeTo, LINE_NAMEname). The full list of new names is in the /fields reference.

NEW-0710-22: Added GET /v1/warehouses/fields — warehouse field schema

A new GET /v1/warehouses/fields endpoint returns the schema of the 19 warehouse fields: for each field — its type, a read-only flag (readonly), a human label, and a description. Warehouses are a custom route (no entity schema), so they previously lacked the field reference that auto-generated entities have. The response is { success: true, data: { fields: { … } } }. Requires the catalog scope.

FIX-0710-23: app publish recovers from a placement drift

Before

On publish (POST /v1/apps/:id/publish) or a placement update (PATCH /v1/apps/:id), if a placement was registered on the Bitrix24 side but missing from the app (drift after an unpublish), the bind failed with "Handler already binded" and the placement stayed out of sync.

After

On that error the platform sweeps the stale binding once and retries — the placement syncs automatically. Recovery fires only on the confirmed conflict, so a live placement is never stripped by mistake; placements that need non-portable OPTIONS (chat widgets, the background worker) are excluded from auto-recovery and still surface a warning.

FIX-0710-24: storage: object sha256 is populated on direct upload

Before

The sha256 field in the storage-object upload response was always null for user objects, even though the schema described it as "computed on upload".

After

For direct upload (POST /v1/storage/objects/upload, files up to 10 MB) sha256 now carries the SHA-256 hash of the object content — usable for integrity checks and duplicate detection (identical content yields an identical hash). Presigned and multipart uploads send bytes straight to storage bypassing the platform, so sha256 stays null there for now.

FIX-0710-25: pacing.active in GET /v1/ai/quota no longer reports an active limit when the quota is zero

Before

With pacing enabled on a Bitrix24 account whose monthly quota is zero (a hard stop via the monthlyVibes = 0 override, or usage not yet initialized), data.pacing.active returned true — even though a 429 ai_pacing_limited rejection is structurally impossible in that state: requests are rejected by the monthly limit, not by a pacing window.

After

data.pacing.active returns true only when exceeding the day or week window can actually produce 429 ai_pacing_limited. With a zero quota the field honestly reports false. The response shape is unchanged; clients that built backoff logic on active need no changes — the signal is simply more accurate.

2026-07-09

FIX-0709-1: transient DB overload now returns 503 with Retry-After instead of 500

Before

In a rare form of transient DB overload (connection exhaustion), some requests (including POST /v1/infra/servers) returned 500.

After

Such requests return 503 with code POOL_EXHAUSTED and a Retry-After header. The error is transient — retry the request with backoff.

Impact on integrators

Clients that retry on 5xx must now handle 503 and honor Retry-After. POST /v1/infra/servers is non-idempotent — a retry can create a duplicate server, so retry with backoff rather than immediately.

FIX-0709-2: placements/bind reports the Bitrix24 plan requirement clearly

Before

Binding a placement with an app key on a portal whose Bitrix24 plan does not permit developer-key REST calls made Bitrix24 deny access, and POST /v1/placements/bind returned an opaque 502 BITRIX_UNAVAILABLE with no cause. The plan is checked on the developer-key path, but GET /v1/me gave no advance signal about this prerequisite.

After

The denial is now classified: POST /v1/placements/bind returns 403 with code INT_TARIFF_REQUIRED and a readable userMessage — the developer-key path needs a commercial Bitrix24 plan on the portal. An app key's GET /v1/me gains a placements.bindPrerequisite block that describes the Bitrix24-side prerequisite up front and lists the error codes. Other bind failures (unknown clientId, stale embedding) still return 502 BITRIX_UNAVAILABLE.

Impact on integrators

No action required, successful calls are unchanged. If you handled 502 BITRIX_UNAVAILABLE on bind, also handle 403 INT_TARIFF_REQUIRED and prompt the user to upgrade the portal to a commercial Bitrix24 plan.

FIX-0709-3: bindPrerequisite in GET /v1/me now reflects the portal plan

Before

The placements.bindPrerequisite block on an app key described the bind prerequisite identically for every portal: subscriptionRequired: true, a remedy that does not apply to a portal whose access is granted by the Bitrix24 plan, and an errorCodes list carrying codes such a portal never receives. On it the bind denial arrives as INT_TARIFF_REQUIRED.

After

The block now depends on the portal. On a portal whose access is granted by the Bitrix24 plan, subscriptionRequired is false, the note describes the commercial-plan requirement, and errorCodes carries INT_TARIFF_REQUIRED and BITRIX_UNAVAILABLE — only the codes that portal can actually receive.

Impact on integrators

Successful calls are unchanged. If you read errorCodes from bindPrerequisite as an exhaustive list, note that it is now narrowed to the codes reachable on the calling portal. The codes themselves and the behavior of POST /v1/placements/bind are unchanged.

BC-0709-4: CRM, task and landing fields aligned with the real Bitrix24 contract

Old format supported until: 09.01.2027

Before

GET /v1/deal-categories returned isLocked as the string "Y"/"N", and GET /v1/payments returned paySystemIsCash as the string "Y"/"N". GET /v1/leads exposed the internal searchContent field (Bitrix24's full-text search index) in every response. The payment fields paySystemXmlId, dateMarked, dateResponsibleId passed through as-is, with no date normalization.

After

isLocked (pipelines) and paySystemIsCash (payments) are now boolean (true/false). searchContent is no longer returned by GET /v1/leads. Payments now declare paySystemXmlId (string), dateMarked and dateResponsibleId (datetimes normalized to a single ISO form with a Z suffix). Tasks gained changedBy/closedBy/statusChangedBy (read-only — writing them returns 400 READONLY_FIELD). GET /v1/currencies/fields now returns human-readable label values and a lang field; GET /v1/deal-categories/fields returns label values; GET /v1/sites/fields sets a nullable flag on fields that may come back empty.

What integrators should do

Read isLocked and paySystemIsCash as boolean instead of comparing to the string "Y". If your code relied on the leads searchContent field, stop: it was an internal, undocumented field.

FIX-0709-5: creating an entity with an empty body is rejected with an explicit error

Before

Creating an entity with an empty body (or no body at all but a Content-Type: application/json header) reached Bitrix24 and silently created an entity with default values — including deals, leads, contacts, companies, invoices and smart processes. A retry or a bodyless client thus spawned junk records in CRM. This affected all three create surfaces: single POST /v1/{entity}, per-entity POST /v1/{entity}/batch and global POST /v1/batch.

After

Such a request returns 400 EMPTY_CREATE_BODY (a per-item error in batch calls) before Bitrix24 is called, on all three surfaces. A meaningful create always carries at least one field — send the fields you need in the request body. Entities that already validate their required fields behave as before.

FIX-0709-6: releasing a server lock accepts an empty JSON body

Before

DELETE /v1/infra/servers/:id/lock with a Content-Type: application/json header and an empty body returned 400 (empty JSON body). Releasing a stuck lock required sending an explicit {}, and a client unaware of that hit a dead end.

After

An empty body with that header is accepted as {}; a bodyless request works and releases the lock. An explicit {} still works too.

FIX-0709-7: portal events now wake a sleeping galaxy app

Before

A Bitrix event sent to a sleeping galaxy app's subscription did not wake it — delivery retried and was lost after the attempts ran out.

After

The platform wakes a sleeping galaxy app on event delivery and delivers the event once it is up — same as for a standalone server.

NEW-0709-8: camelCase keys inside communications when creating an activity

POST /v1/activities now accepts the nested keys of communications items in camelCase (type, value, entityTypeId, entityId) — consistent with the rest of the API. Previously the nested keys were accepted only in Bitrix24 UPPER case (TYPE, VALUE, ENTITY_TYPE_ID, ENTITY_ID), and the camelCase form was silently dropped — communications: [{ "type": …, "value": … }] returned 422 "COMMUNICATIONS is not defined or invalid", while [{ "TYPE": …, "VALUE": … }] created the activity. UPPER case still works; if both forms of the same key appear in one object, the UPPER-case one wins.

FIX-0709-9: unpublishing removes the tab across all of the app's handlers

Before

POST /v1/apps/:id/unpublish unbound the placement only by the handler matching the assumed platform address. If the tab was bound to the app's own technical address, Bitrix24 did not find it and did not remove it — the tab stayed stuck in the CRM card and could no longer be removed via the API.

After

Unpublishing now removes the placement across all of the app's handlers, including one bound to the server's technical address — the orphaned tab disappears.

NEW-0709-10: Key self-description in GET /v1/guide

The GET /v1/guide response now carries a data.keysAuth block. It describes the two self-description endpoints — GET /v1/me and GET /v1/guide — and links to their documentation: the response contract of each, the key access mode, and the overview of key types.

Both endpoints work with the X-Api-Key header alone, no session token is required.

The field is additive: existing clients are unaffected.

Affected endpoints: GET /v1/guide

FIX-0709-11: GET /v1/{entity}/:id now honours ?select=

The ?select= field projection was ignored on single-record reads by id: the response always came back with every field, even though /v1/me states ?select= works "on list, get by id, and POST /search". Get-by-id now projects the response the same way list and search do, bringing the behaviour in line with what was documented.

Before

GET /v1/leads/42?select=id,title returned the full record (all fields).

After

GET /v1/leads/42?select=id,title returns only id and title. The comma (?select=id,title), array (?select[]=id&select[]=title) and indexed (?select[0]=id&select[1]=title) forms are all accepted; id is always included in the response. An unknown field name is silently skipped — it is not an error. When ?select= and ?include= are combined, the included relation is preserved in the response. The indexed form (?select[0]=…) previously returned 500 on the list endpoint GET /v1/{entity} too — it now projects correctly there as well.

FIX-0709-12: /fields for orders, basket items, requisite presets and document templates now matches the real B24 contract

Before

GET /:entity/fields (and the OpenAPI schema generated from it) declared fields that Bitrix24 never returns: provider on document templates; reserved, sumPaid, dateBill, datePayBefore, datePaid, empPaidId, userEmail, userName at the order top level; module, fUserId, lid, dateRefresh, subscribe, reserved, reserveQuantity on basket items; originatorId on requisite presets. Filtering and sorting by those fields silently did nothing. Meanwhile real fields were left undeclared: requisiteLink on the order, type/properties/reservations on the basket item. Requisite presets accepted countryId/entityTypeId on update, where Bitrix24 silently ignores them.

After

The non-existent fields are removed from /fields and OpenAPI. The real fields are declared: on the order — requisiteLink (object requisiteId/bankDetailId/mcRequisiteId/mcBankDetailId, read-only); on the basket item — type, properties, reservations (read-only). On requisite presets, countryId and entityTypeId are marked create-only: an update returns 400 READONLY_FIELD instead of a silent no-op.

Impact on integrators

list/get responses are unchanged — the removed fields were never returned. If a request filtered or sorted by a removed field it now returns 400 — use the real fields from /fields (for example, an order's payment dates and sums live inside the payments array, not at the top level). Updating countryId/entityTypeId on a requisite preset is now explicitly rejected — set those fields on create only.

Affected endpoints: GET /v1/orders/fields, GET /v1/basket-items/fields, GET /v1/requisite-presets/fields, GET /v1/doc-templates/fields

NEW-0709-13: GET /:entity/fields returns human-readable labels and descriptions for deals, leads, invoices, activities, statuses and timeline comments

The response of GET /v1/deals/fields, /v1/leads/fields, /v1/invoices/fields, /v1/activities/fields, /v1/statuses/fields and /v1/timelines/fields now carries a human-readable label and description for every field, returned in English. Fields with magic-number codes gained enum dictionaries: deals — stageSemanticId (P — in progress, S — success, F — failure); activities — typeId, direction, priority, status, notifyType and descriptionType. Existing calls keep working unchanged — these are additive metadata fields, the response shape does not change.

FIX-0709-14: POST /v1/batch — unified sub-call error shape and totals only for list/search

Before

A Bitrix24 error inside a successful 200 POST /v1/batch response (for example, a get of a missing element) landed in data.errors[<id>] in Bitrix24's native { error, error_description } form — not the V1 envelope { code, message } used by validation errors and every other API response. Meanwhile data.totals[<id>] was populated for any action, including get/create/update/delete, where a lone number next to a single record is meaningless.

After

A sub-call error is mapped to { code, message } (errorcode, error_descriptionmessage), consistent with all other errors. data.totals[<id>] is populated only for list and search actions, where a match count actually has meaning.

FIX-0709-15: GET /v1/openline-configs — empty-value normalization in the response

Before

GET /v1/openline-configs and GET /v1/openline-configs/:id returned service fields in shapes awkward for clients: KPI_FIRST_ANSWER_LIST, KPI_FURTHER_ANSWER_LIST, DEFAULT_OPERATOR_DATA came back as null (client .map/.length broke on them); AUTO_CLOSE_TEXT for an unset value came back as "" in the card but null in the list; WORKTIME_HOLIDAYS/WORKTIME_DAYOFF for an empty set came back as [""] (an array with one empty string).

After

Lists are normalized: null[]. AUTO_CLOSE_TEXT converges on a single null for an unset value in both the list and the card. WORKTIME_HOLIDAYS/WORKTIME_DAYOFF return [] for an empty set. The list and the card now return the same shape for these fields.

FIX-0709-16: GET /v1/users/fields returns the possible values (items) for enumeration user-fields

Before

A custom enumeration user-field (UF_USR_* of the "list" type) came back from GET /v1/users/fields as { "type": "string", "label": "…" } — with no list of possible values. Reason: for UF fields the user.fields method returns only a label, not the type or the variants, so an enumeration was indistinguishable from a string.

After

Such a field now carries its real type and the list of variants: { "type": "enumeration", "label": "…", "items": [ { "ID": "…", "VALUE": "…", "DEF": "…", "XML_ID": "…" }, … ] }. The values are read from user.userfield.list — the key needs the user.userfield scope for this; without it the field is still returned with its label but without items (graceful degradation). Other UF fields (money, date, …) likewise now show their real type instead of string.

FIX-0709-17: the empty-queue hint now appears after sustained emptiness

Before

GET /v1/bots/:botId/events incremented the empty-response counter on exactly every request, and the hint field appeared strictly after the fifth consecutive empty response. The number in the hint text matched the number of requests made.

After

The empty-response counter is updated periodically rather than on every request, so hint appears after the queue stays empty for a sustained period — at the recommended 2–5 second polling interval, after roughly a couple of minutes of continuously empty polling. The number N in the text reflects the count of observed empty periods, not the exact number of requests made. The "an event was delivered → counter and hint reset" rule is unchanged.

Impact on integrators

No code change is needed. If you relied on hint appearing strictly on the fifth request or read N as an exact request count — use persisted and the presence of events as the primary signal, and treat hint as a diagnostic hint.

2026-07-08

FIX-0708-1: GET /v1/doc-templates and POST /search now honour order and offset

Before

The order and offset parameters on GET /v1/doc-templates and POST /v1/doc-templates/search were silently ignored: the list always came back sorted ascending by id, and offset did not move the window. The underlying Bitrix24 method returns templates as an id-keyed object, and the requested order was lost while unwrapping the response.

After

Sorting (order[field]=asc|desc, including multi-field) and windowing (offset/limit) are applied on the Vibecode side: the full template set is fetched, sorted, and sliced to the requested window. total and hasMore are computed from the actually collected set.

Impact on integrators

If you relied on the implicit ascending-by-id order at offset=0 with no sort, nothing changes — that stays the default. POST /v1/doc-templates/batch (batch list) is unaffected. String order (name, region) is byte-wise, not locale-aware.

NEW-0708-2: GET /v1/apps/:id/sources — new linkedServerSources field

GET /v1/apps/:id/sources now additionally returns a linkedServerSources field — source versions stored under a server (via POST /v1/infra/servers/:id/sources or auto-save on deploy), grouped by server, each with its own serverContext. Such versions previously did not appear in this response when saved under a personal key — they are now visible.

The field is additive: versions, totalVersions, currentVersionId and totalSizeBytes are unchanged and still list only app-scoped versions. Alongside it come linkedServerSourcesTruncated (a truncation flag for very large histories) and linkedServerHint, which points to GET /v1/infra/servers/:serverId/sources — the authoritative full list and download for those versions. The section is populated for the app author (personal key) and the portal administrator; it is empty when called with an OAuth-app key, and is not computed on a ?sha256= probe.

NEW-0708-3: preemptible field on the server plans list response

The GET /v1/infra/providers/:id/plans response now formally documents the preemptible field on each plan. A preemptible plan is cheaper, but the cloud force-restarts such a machine roughly once a day — it is not suitable for continuous 24/7 workloads. For a server, agent or bot that must run without interruption, pick a non-preemptible plan (preemptible is false or absent).

The field was already returned by the runtime — this entry formalizes it in OpenAPI and the docs; no change to existing integrations is required.

NEW-0708-4: GET /v1/models/:id now reports a retired model's successor price and a replaced_by field

For a model that has been retired, the by-id detail request now returns a replaced_by field with the identifier of the successor model that actually serves the calls, and the pricing field shows that successor's price — the price the request is billed at. Previously pricing showed the retired row's own zero price, making the model look free while a paid successor served the calls. Ordinary models and existing calls are unchanged.

FIX-0708-5: list auto-pagination preserves row order for limits above 550

Before

Auto-paginated list reads — GET /v1/{entity}?limit=… and POST /v1/{entity}/search — returned rows out of order when limit exceeded ~550: internal result pages were merged in the wrong sequence relative to what Bitrix24 returned, so the order parameter was not honored across the final array. When the collection held more rows than limit, the window trim could drop rows from the middle of the sorted set while keeping later ones.

After

Pages are merged strictly in Bitrix24 return order: rows arrive in the requested sort for any limit, and the limit trim no longer drops rows from the middle of the set because of wrong merge order.

Impact on integrators

No changes required. If you re-sorted large result sets on your side as a workaround, that is no longer needed.

FIX-0708-6: auto-pagination no longer silently drops a page when a batch sub-request fails

Before

With limit > 50 the list is assembled from batch sub-requests of 50 records each. If Bitrix24 rejected one sub-request (most often on its request limit — QUERY_LIMIT_EXCEEDED), that page silently fell out of the middle of the set: the response stayed 200 with an undetectable 50-record hole in the data (e.g. records 1–200 and 251–600 without 201–250), while meta.total and meta.hasMore looked self-consistent.

After

For lists, plain search, batch sub-calls and aggregations the result is always a contiguous prefix of the set: records past the failed page are dropped, meta.hasMore stays true, and the response carries meta.pageErrorSample { code, message } with the failure reason — mirroring windowed search's meta.windowErrorSample. The field is added to list responses (e.g. GET /v1/deals), to POST /v1/{entity}/search (e.g. deals), to per-sub-call meta of POST /v1/batch and to data.meta of POST /v1/{entity}/aggregate (where it explains why recordsProcessed is below totalRecords). In windowed search (a wide date range) the set is assembled from windows, so a page lost inside one window can leave that window's tail missing — there the incompleteness signal is meta.pageErrorSample itself, not meta.hasMore. In all cases the field appears only when the returned page is actually shorter than limit: a full page is never flagged with a false alarm. Incomplete responses are never cached: a retry goes straight to Bitrix24.

Impact on integrators

No client changes required: result sets that could previously contain an invisible hole are now correct, and the shortfall reason is visible in meta.pageErrorSample. Fetch the remainder by retrying with offset equal to your original offset plus the number of records received — except for windowed searches over a wide date range (offset is not supported there: narrow the range or retry later).

BC-0708-7: structured output: a truncated or empty result now returns 422 instead of an empty 200

Old format supported until: 08.07.2026

Before

POST /v1/chat/completions with response_format (json_object or json_schema) could return 200 with content: null (or a truncated, unparseable JSON string) plus a warning that clients ignored, when generation was cut off. This happened most often on reasoning models: the reasoning phase consumed the whole max_tokens budget before the model produced the JSON. The response looked successful but could not be parsed.

After

Such a request now returns 422 with code: "structured_output_truncated", plus finishReason, suggestedMaxTokens (a larger max_tokens to retry with), and param: "max_tokens". In streaming mode a {"error":{"code":"structured_output_truncated"}} frame is emitted before data: [DONE] — read the stream through to [DONE]. Additionally, for free reasoning models given a too-small max_tokens the platform raises the budget to a safe minimum and tags the successful response with a MAX_TOKENS_RAISED warning. The truncated attempt still consumes and bills tokens.

What integrators should do

Handle 422 structured_output_truncated in your error branch and retry with a larger max_tokens (you can use the suggestedMaxTokens value). For strictly deterministic JSON, set a generous max_tokens or use a non-reasoning model.

2026-07-07

FIX-0707-1: smart-processes: linkedUserFields accepts Y/N and boolean values

Before

POST /v1/smart-processes and PATCH /v1/smart-processes/:entityTypeId with linkedUserFields only worked when the flag value was strictly "true"/"false". A value in the "Y"/"N" convention (used by every other smart-process field) or a boolean true/false was silently ignored: the request returned success: true, but the display in the user field was not enabled.

After

linkedUserFields values are normalized the same way as the nested relations[].isChildrenListEnabled flag: true/"Y"/"yes"/1 → enabled, false/"N"/"no"/0 → disabled. Existing calls with "true"/"false" keep working unchanged.

Impact on integrators

No action needed — calls that previously "silently did nothing" with "Y" are now applied correctly.

BC-0707-2: Order-card nested fields normalized

Old format supported until: 06.01.2027

Before

GET /v1/orders/{id} returned the nested clients, payments, basketItems arrays in raw Bitrix24 shape: boolean fields as "Y" and "N" strings (payments[].paid, clients[].isPrimary, basketItems[].vatIncluded, and others), dates inside payments and basketItems with a +03:00 offset, and companyId set to 0 when no company is bound. The accountNumber field was silently ignored on create and update.

After

Nested Y/N fields now arrive as boolean (true or false); nested dates are normalized to UTC (ending in Z); companyId is null when no company is bound instead of 0; accountNumber became read-only — sending it on create or update returns 400 with code READONLY_FIELD.

What integrators should do

Read nested Y/N fields as boolean instead of comparing to the string "Y"; treat null instead of 0 as "no company bound"; stop sending accountNumber in the create and update body — the number is assigned automatically.

FIX-0707-3: The /v1/openapi.json spec now matches actual runtime

Before

The machine OpenAPI spec was generated from static entity metadata and drifted from real responses: no field was marked nullable, nested order-card arrays were typed as a string, list methods were missing filter and select, and operations declared only success codes and 403.

After

The spec now reflects the contract. Nullable fields are emitted as type: ["<type>", "null"]. Object and array-of-object fields are typed honestly, including the nested clients, payments, basketItems, propertyValues of GET /v1/orders/{id}. List methods declare the filter and select query params. Operations carry the standard error codes 400, 401, 404, 422 in the single { success:false, error:{ code, message } } envelope. *Input schemas declare create-required fields. Additionally GET /v1/orders/fields returns clients as an array instead of object. An SDK generated from the spec now types responses correctly.

BC-0707-4: /search: auto-windowed search now returns the real Bitrix24 error on total failure

Old format supported until: 07.09.2026

Before

Any failed auto-windowed POST /v1/{entity}/search returned 502 { "error": { "code": "WINDOWED_SEARCH_FAILED" } } with a generic "add autoWindow:false".

After

The response matches the same query at a narrow range — the real code and message: a rejected filter/sort field → 400 UNKNOWN_FILTER_FIELD / 400 INVALID_PARAMS; no access → 403; request limit / queue overload → 429 + Retry-After; timeout → 503; Bitrix24 unavailable → 502 BITRIX_UNAVAILABLE. Partial window failure (status 200) now carries meta.windowErrorSample { code, message }.

What integrators must do

If you branched on error.code === "WINDOWED_SEARCH_FAILED" (e.g. to retry with autoWindow:false) — branch on the real codes instead. The autoWindow:false workaround remains; it is useful where it actually helps (the 429 QUEUE_TIMEOUT hint names it). The total-failure response no longer carries the meta block (autoWindowed/windowCount/windowErrors) — the signal is now in the error code/message itself; meta.windowErrorSample remains on partial failure (status 200).

FIX-0707-5: lists on a portal without the module return 409 consistently, not 429

Before

On a portal where the Universal Lists module is disabled, calls to /v1/lists returned the clear 409 LISTS_MODULE_NOT_ENABLED only for the first few requests. After that the built-in error-loop protection tripped and every subsequent call returned 429 ERROR_LOOP_DETECTED, hiding the real cause (the module is not installed).

After

The "method unavailable on this portal" signal is no longer counted by the error-loop protection, so lists.* calls on a module-off portal return 409 LISTS_MODULE_NOT_ENABLED consistently no matter how many times they repeat. The response stays actionable: enable the module on the portal and retry.

NEW-0707-6: error.hint on the 400 for a server create missing source and provider/plan/region

POST /v1/infra/servers, when rejected with 400 INVALID_REQUEST because provider/plan/region are missing (and no source was passed) on a galaxy-placement portal, now additionally returns an error.hint object with reason (why the request was rejected on this portal), recovery (the recommended one-shot path plus the working two-step alternative) and example (a paste-ready one-shot body skeleton). error.code and error.message are unchanged — the hint is strictly additive; portals without galaxy placement get the previous response, without hint.

The hint is also returned on 400 RUNTIME_PARAM_REMOVED (a create with runtime but no source on a galaxy-placement portal), and a body carrying placement: "dedicated" gets a separate hint variant — for a dedicated server, keeping the intent and adding the missing provider/plan/region tuple, instead of steering the caller into a galaxy container.

FIX-0707-7: Galaxy deploy checklist in /v1/me now matches the actual contract

Before: step 2 of deployment.galaxyApp.checklist instructed POST /v1/infra/servers { name } with no source and no provider/plan/region — that call always failed with 400 INVALID_REQUEST. The CREATE rule did not explain that the two-step path requires the full provider/plan/region tuple, and newAppPlacement.note promised a dedicated standalone VM where the create actually returns a galaxy slot with next: "deploy". The favicon guide pointed to the same broken order; the never-deployed-slot reap window was stated as "~12-20 min" versus the actual ~20-25.

After: the recommended path is a single call — POST /v1/infra/servers { name, source, runtime, start } (omit provider/plan/region). The two-step path is documented truthfully: a create without source requires the full provider/plan/region tuple (values are informational for a galaxy — the app inherits its host), and on galaxy placement returns a slot with next: "deploy"; a never-deployed slot is reaped to ERROR after ~20 min (swept every 5 min). Favicon: the primary path is a self-hosted /icon.svg inside the archive (no id needed); the platform-hosted URL remains an alternative via the two-step order or a re-deploy. The status-polling step gained a status=error branch → provisionError/buildLog → re-deploy.

Impact on integrators: agents following the checklist now deploy on the first call. Endpoint behavior is unchanged — only the /v1/me texts and the /v1/openapi.json description were updated; existing integrations keep working as is.

NEW-0707-8: Company AI quota is available via the API

The new GET /v1/ai/quota endpoint returns the monthly AI quota state of your Bitrix24 account: the percentage of the limit consumed (pctUsed, an honest value — above 100 on overspend), the exhaustion flag (exhausted), the reset date (resetAt, a rolling 30-day window), and a per-model breakdown — request counts, tokens, and each model's share of the monthly limit (byModel[].pctOfLimit). Absolute limit values in Vibe credits are not exposed — percentages only, same as the dashboard. Requires the vibe:ai scope.

2026-07-06

NEW-0706-1: pricing.perCall and pricing.perMinute fields in the model catalog

GET /v1/models and GET /v1/models/{model} responses gain optional fields in the pricing object: perCall — the cost of a single request in Vibe credits, perMinute — the cost of one minute of audio in Vibe credits (for speech-to-text models). The fields appear only for models whose corresponding base price is above zero; for all other models the pricing object is unchanged — existing requests keep working as before.

NEW-0706-2: New 402 error code ai_quota_exhausted on AI endpoints

When the portal monthly AI quota control is active, POST /v1/chat/completions, POST /v1/embeddings and POST /v1/audio/transcriptions may return 402 with { success: false, error: { code: "ai_quota_exhausted", type: "insufficient_quota", reason, resetAt? } }. The reason field distinguishes three cases: breaker — the hourly over-quota spending limiter fired, wallet_empty — the quota is exhausted and the portal balance has no funds, wallet_off — over-quota usage is not available for this portal. resetAt is when requests will pass again (may be absent for a permanently disabled portal). While the portal quota is not exhausted, endpoint behavior is unchanged.

FIX-0706-3: Over-quota AI usage is charged at the model's base catalog price

Before

With the portal monthly AI quota control active, over-quota requests to POST /v1/chat/completions, POST /v1/embeddings and POST /v1/audio/transcriptions were charged to the portal balance at internal quota-program rates — with off-peak discounts applied; the effective price was not visible in the model catalog.

After

Over-quota usage is charged at the model's base price from the public catalog — the same one returned in the pricing field of GET /v1/models, including the new perCall and perMinute for non-token models. Off-peak discounts apply only to quota consumption, not to the money balance. Usage within the quota is still not charged to the portal balance.

Impact on integrators

No changes required. The cost of over-quota usage can now be computed upfront from the model's catalog price.

NEW-0706-4: The bitrix/embeddings model is available in the API

The POST /v1/embeddings endpoint is now served by the bitrix/embeddings model — turning text into vector representations for semantic search, clustering, and retrieval (RAG). The model is free and billed on input tokens only. For the list of embedding-capable models see GET /v1/models.

FIX-0706-5: Deploy reports an honest error when the new build did not bind the port

Deploy via POST /v1/infra/servers/:id/deploy now verifies the port is held by the NEW service. If a previous process keeps listening and the new build crash-loops with EADDRINUSE, the deploy fails honestly instead of falsely succeeding; a port held by a leftover process of the same app is freed automatically where provable.

Before

The old version kept answering 200, the deploy reported success, and the new build never came up — with no error and no hint.

After

The healthcheck step returns an error naming EADDRINUSE and the port, and the stop_existing step frees the port from a leftover process of the app (or warns and proceeds when it cannot).

NEW-0706-6: filter: $nin (NOT IN) operator to exclude a set of values

Before

There was no way to select records whose field is NOT in a set of values: the $in (IN) operator existed, but its inverse did not. The Bitrix24-native field-name prefixes @ (IN) and !@ (NOT IN) ({ "!@categoryId": [1, 3] }) were not translated — such a deal filter returned 400 UNKNOWN_FILTER_FIELD.

After

Added the $nin operator: { "filter": { "categoryId": { "$nin": [1, 3] } } } returns records of every value except those listed (NOT IN). Symmetric to $in. The Bitrix24-native @ / !@ field-name prefixes are still unsupported, but now return a clear 400 INVALID_FILTER_FIELD hinting to switch to $in / $nin, instead of a confusing error or a silently-ignored (thus full-set) filter.

BC-0706-7: dedicated error code for an oversized exec command

Old format supported until: 06.01.2027

Before

A command longer than 10000 characters at POST /v1/infra/servers/:id/exec was rejected with the generic VALIDATION_ERROR code, with no cause and no way out.

After

Such a request returns 400 with the dedicated COMMAND_TOO_LONG code and a structured hint: ship large payloads and scripts via POST /v1/infra/servers/:id/upload, then run them with bash /path/script.sh. All other schema violations still return VALIDATION_ERROR.

What integrators should do

If your client handles VALIDATION_ERROR of this endpoint as the catch-all validation case — add handling for the COMMAND_TOO_LONG code (or treat any 400 uniformly).

NEW-0706-8: hint in the exec timeout error

The EXEC_TIMEOUT error of POST /v1/infra/servers/:id/exec now carries a structured hint field (reason / recovery / recoveryAction): why the process was stopped (at timeout the whole process group is terminated forcibly, with no grace period) and what to do — run long operations as a detached background job and monitor it via GET /v1/infra/servers/:id/logs, raise timeout up to 600 seconds, or use ?stream=true. The field is additive: the previous code / message shape is unchanged, and the hint arrives both in JSON mode and in the SSE error event.

2026-07-05

FIX-0705-1: Chat message send — a clear error when the text is empty

The message text goes in the message field. Previously a call to POST /v1/chats/{dialogId}/messages that carried the text under an unknown field name (e.g. {"text": "hi"}) silently dropped that field, and Bitrix24 returned a 422 BITRIX_ERROR about an empty message — even though content was supplied.

Before

{"text": "hi"}422 BITRIX_ERROR about an empty message, with no hint at the cause.

After

The same call now returns 400 MESSAGE_REQUIRED right away and lists the unrecognized field(s), pointing at the message field. Empty text is still allowed together with an attach block (attachment-only message).

Impact on integrators

Correct calls that use the message field are unchanged. The error for a wrong field name is now precise.

FIX-0705-2: API key in the Authorization: Bearer header — a clear error instead of INVALID_SESSION

The API key goes in the X-Api-Key header. Previously, if the key was mistakenly placed in Authorization: Bearer (that slot is for an OAuth app session token), the server returned 401 INVALID_SESSION, so integrators chased an OAuth-session problem when the real cause was the wrong header.

Before

A vibe_app_* key in Authorization: Bearer401 INVALID_SESSION.

After

The same request now returns 401 WRONG_AUTH_SCHEME with a hint: an OAuth app key (vibe_app_*) goes in X-Api-Key, and Authorization: Bearer carries the session token (vibe_session_*) from POST /v1/oauth/token; a client that can only send Bearer can use a personal key (vibe_api_*). Session tokens and personal keys in Bearer are unaffected.

Impact on integrators

Correct calls with the key in X-Api-Key and the session in Authorization: Bearer are unchanged.

FIX-0705-3: multipart/create rejects XSS-prone content types for PUBLIC objects

Before

For PUBLIC objects the content types text/html, application/javascript, application/x-javascript and image/svg+xml were rejected on direct and presigned uploads, but not when starting a multipart upload. Calling POST /v1/storage/objects/multipart/create with visibility = PUBLIC and such a type created a session, and once completed the object was served inline in the browser.

After

POST /v1/storage/objects/multipart/create with visibility = PUBLIC and an XSS-prone content type now returns 415 STORAGE_FORBIDDEN_CONTENT_TYPE, the same as direct and presigned uploads. No multipart session is opened. PRIVATE objects still accept any content type.

Impact on integrators

Behavior now matches what the Storage reference documents: XSS-prone content types are not allowed for PUBLIC objects on any upload path. To multipart-upload such a file, use visibility = PRIVATE or a safe content type.

FIX-0705-4: binding a placement to a server's technical URL now routes through the platform handler

Before

POST /v1/placements/bind accepted a handler pointing at the app's technical Black Hole URL (app-*.vibecode…) and registered it with Bitrix24 verbatim. Bitrix24 posted the placement iframe straight to that URL, bypassing the platform: no session was minted, and on a server restricted to Bitrix24 users the placement looped on the login gate (on a public server the app returned its own 404 error).

After

Such a handler is automatically rewritten to the app's platform handler (/v1/bitrix-handler) — the placement opens and authenticates normally. The response gains handlerRewritten: true and requestedHandler with the original value. External (non-Black Hole) handlers are left unchanged. If the app's platform handler cannot be resolved, the bind is rejected with code PLATFORM_HANDLER_UNRESOLVABLE instead of registering an unusable URL. Additionally GET /v1/placements flags an already-misbound handler: data.handlers[].misbound: true plus a textual warnings[].

Additionally, if the placement is already registered in Bitrix24 but missing from the app's list (drift — e.g. after an unpublish that didn't unbind on Bitrix24), the bind no longer fails with "Handler already binded": the platform clears the stale binding and retries the request once, healing the drift. If the bind fails for another reason (for example a commercial Bitrix24 tariff is required), the working placement is left in place.

NEW-0705-5: New 402 error code ai_quota_exhausted on AI endpoints

When the portal monthly AI quota control is active, POST /v1/chat/completions, POST /v1/embeddings and POST /v1/audio/transcriptions may return 402 with { success: false, error: { code: "ai_quota_exhausted", type: "insufficient_quota", reason, resetAt? } }. The reason field distinguishes three cases: breaker — the hourly over-quota spending limiter fired, wallet_empty — the quota is exhausted and the portal balance has no funds, wallet_off — over-quota usage is not available for this portal. resetAt is when requests will pass again (may be absent for a permanently disabled portal). While the portal quota is not exhausted, endpoint behavior is unchanged.

2026-07-04

BC-0704-1: Overload rejections: 429/503 instead of 504

Old format supported until: 31.07.2026

Overload rejections changed HTTP status codes (body codes are UNCHANGED — only the status changes). Rule of thumb: 429 — the request was NOT processed, safe to retry after Retry-After (the header is now always set); 503 — the platform or upstream is unhealthy, retry later; for write operations verify whether the change was applied before retrying. Application-level 504 is removed from the API.

What changed: QUEUE_OVERFLOW 503→429; QUEUE_TIMEOUT 504→429; BITRIX_TIMEOUT (Bitrix24 did not respond within 15 seconds — the write may have been applied, re-read the entity before retrying) →503; ai_provider_timeout 504→503; UPSTREAM_TIMEOUT (web search /v1/search — the upstream provider did not respond in time) 504→503; RUNTIME_TIMEOUT / GATEWAY_TIMEOUT / WAKE_TIMEOUT 504→503.

NEW-0704-2: New AI overload code: 429 ai_congested

AI requests to the platform cluster now pass through an admission gate: on pool overload the response is 429 with body code ai_congested and a Retry-After header. Retry is safe (the request never ran, no charge). BYOK keys and external providers are not affected. Disabled by default — enabled by the platform.

BC-0704-3: read-only (READONLY) keys can no longer write on hand-written endpoints

Old format supported until: 03.07.2026

Before

An APP key with the accessMode: READONLY access mode still reached the write to Bitrix24 on a number of hand-written endpoints (requisites and presets, user fields, timeline pin/note/bind, telephony, mail, disk, business processes, user invite and deactivation, and others) — the guard checked only the scope, not the key access mode.

After

Any write attempt with a read-only key returns 403 with the WRITE_BLOCKED_READONLY_KEY code. Read endpoints are not affected.

What integrators should do

If your integration was writing with a read-only key, switch the key to read and write mode on the key management page.

FIX-0704-4: galaxy app deploy now unpacks wrapped archives correctly and reports an empty source.content clearly

Before

POST /v1/infra/servers/:id/deploy for a galaxy app (kind=GALAXY_APP) whose source.content project files sat inside a single wrapping folder (a typical macOS-built zip) built the app with an empty build context and failed at runtime with npm error enoent Could not read package.json. When source.content did not unpack into any files at all (a versionId, a path, or an empty archive), the deploy raised the same confusing build error.

After

Such archives now deploy correctly — the app files end up at the build-context root. And when source.content unpacks to an empty context, the deploy returns GALAXY_APP_BUILD_FAILED right away with a clear empty-build-context message, hinting that source.content must be a base64 archive (tar.gz or zip) of your project files, not a versionId or a path.

2026-07-03

FIX-0703-1: binding a placement via a developer key no longer returns 500 after an unpublish/republish cycle

Before

POST /v1/placements/bind for a developer-key-managed app (type local.*) could consistently return 500 (BITRIX_UNAVAILABLE, INTERNAL_SERVER_ERROR) when binding a placement (e.g. CRM_DEAL_DETAIL_TAB or CRM_CONTACT_DETAIL_TAB) after the app was unpublished and published again. The previous placement registration remained on the Bitrix24 side, and registering a new one on top of it failed with an internal error. Retries returned the same error.

After

Before registering the placement, the request now clears its previous registration on the Bitrix24 side, so the bind succeeds even after an unpublish/republish cycle. No change to your call.

FIX-0703-2: /v1/sites — filtering by knowledge-base (KNOWLEDGE) and group (GROUP) type is no longer ignored

Before

POST /v1/sites/search (as well as GET /v1/sites and POST /v1/sites/aggregate) with filter[type]=KNOWLEDGE or filter[type]=GROUP silently returned ordinary landing sites (PAGE / STORE / VIBE) instead of knowledge bases or group pages. The cause is on the Bitrix24 side: the landing.site.getList method binds the TYPE filter to an internal area (scope), and without the scope parameter the KNOWLEDGE / GROUP types are not part of the default area — so the type filter was silently dropped. The only workaround was to add scope manually (see List sites).

After

When the filter names one such type and scope is not passed explicitly, Vibecode supplies the matching area itself (type=KNOWLEDGEscope=KNOWLEDGE, type=GROUPscope=GROUP) — and the request returns exactly the knowledge bases / group pages. An explicitly passed scope always wins and is never overridden. If the type is given as a list or operator (for example {"type":{"$in":["KNOWLEDGE","PAGE"]}}), where a single area cannot be chosen, meta.warnings carries a hint with code TYPE_REQUIRES_SCOPE.

Impact on integrators

No action required. Requests with filter[type]=PAGE / STORE / VIBE and requests without a type filter work as before. MAINPAGE is an area, not a site type (its sites have type VIBE), so MAINPAGE is not derived from the type filter. Behavior for /v1/pages is unchanged.

FIX-0703-3: page and site aggregation returns count again

Before

POST /v1/pages/aggregate and POST /v1/sites/aggregate returned count: 0 and meta.totalRecords: 0 even when pages and sites existed — in every form: without a filter, with a filter, with a count expression, and as the top-level count alongside groupBy. Per-group counts under groupBy were already correct.

After

count and meta.totalRecords reflect the actual number of records; the top-level count under groupBy equals the sum of the group counts.

Integrator impact

No action required — the response is now correct.

NEW-0703-4: Quality and timestamp parameters in audio transcription

Audio transcription POST /v1/audio/transcriptions accepts five new optional fields. Recognition quality: prompt — a context hint (conversation topic, style, correct spelling of terms, up to 2000 characters), hotwords — a comma-separated list of special words (rare terms, brands, names, up to 500 characters), vad_filter — a silence filter applied before recognition (fewer hallucinations on recordings with pauses). Output control: temperature — decoder temperature from 0 to 1, timestamp_granularities[] — timestamp granularity word/segment (only with response_format=verbose_json; with word, each segment gains a words array with per-word timing and probability). The fields are passed in multipart/form-data alongside file and are compatible with the OpenAI contract. Invalid values are rejected with the invalid_prompt, invalid_hotwords, invalid_temperature, invalid_vad_filter, and invalid_timestamp_granularities codes.

FIX-0703-5: Apps created via the API now open correctly as placements

Before

Some apps created via POST /v1/apps failed to open when their placement was invoked in Bitrix24 — instead of the app interface the user saw an app-resolution error.

After

Created apps now resolve correctly and open as placement widgets in Bitrix24. The create response is unchanged — the app is immediately ready for publishing and binding placements.

Integrator impact

No action required. Recreate a previously non-opening app (delete and create it again) — the new app opens correctly.

NEW-0703-6: key deletion is blocked while an agent or bot is linked

DELETE /v1/keys/:id now returns 409 with code KEY_HAS_LINKED_AGENT when the key is the control key of a live AI agent or managed bot.

Before

Deleting such a key orphaned the agent and cascade-deleted the bot together with its token — the bot's Bitrix24 identity was lost irrecoverably, with no warning.

After

Response body: { success: false, error: { code: "KEY_HAS_LINKED_AGENT", message, details: { linkedAgentCount, linkedBotCount, agents: [{ id, name, status }] } } }. Rebind the resources to another key or delete the agent/bot first; to restore access for an orphaned agent, use the "Restore access" action in the cabinet. The check runs before Bitrix24 synchronization — on a 409 the Bitrix24-side credentials are left untouched. Sibling of the existing KEY_HAS_ACTIVE_SERVERS.

NEW-0703-7: Task change history and kanban stages

Two read-only endpoints added (scope task). GET /v1/tasks/:taskId/history returns a task's full change history in a single call: kanban stage moves, sprint and backlog moves, statuses and other events. Filter by event type via ?field=STAGE (comma-separate several, e.g. ?field=STAGE,MOVE_TO_SPRINT); sort via ?order=asc or ?order=desc (created-date ascending by default). Each entry carries id, createdDate, field, a value object with the previous and new value, and a user with the author id. GET /v1/tasks/stages/:entityId returns the current kanban columns of a workgroup (N) or personal plan (0).

FIX-0703-8: bizproc-activities: clear error instead of "Wrong handler URL" when handler is missing

Before

POST /v1/bizproc-activities without a handler field (or with the handler URL mistakenly sent under handlerUrl) returned the opaque core error 422 BITRIX_ERROR: Wrong handler URL.

After

code, name, and handler are validated before the Bitrix24 call: a missing handler now returns 400 MISSING_REQUIRED_FIELDS with the message Body field "handler" is required to create bizprocActivity, pointing at the correct field name. Successful calls that include a valid handler are unaffected.

FIX-0703-9: POST /v1/batch — update and delete responses are now normalized like create and get

Before

In the global batch call POST /v1/batch an update sub-call returned its response in a raw wrapper (a nested object instead of the flat record), and a delete sub-call returned an empty array with no success signal. This diverged from create and get in the same endpoint and from single PATCH /v1/{entity}/:id, which return a flat normalized record.

After

An update sub-call returns the flat normalized record (camelCase fields) — like create, get and single PATCH. A delete sub-call returns a success signal of the form { id, deleted: true }.

FIX-0703-10: POST /v1/{entity}/batch — create, update and delete work again for CRM entities

Before

A per-entity batch call to POST /v1/{entity}/batch with a create, update or delete action for deals, contacts, companies, leads, quotes and invoices returned a per-item error "Could not find value for parameter {entityTypeId}", and the record was not created, updated or deleted. Single calls (POST /v1/{entity}) and the global POST /v1/batch worked on the same entities.

After

Per-entity batch create, update and delete for these entities now succeed — the same way single calls and the global batch endpoint do.

FIX-0703-11: Omitting the model field in chat again falls back to the default model

Before

POST /v1/chat/completions without a model field returned 400 no_default_model on portals where no default model was explicitly set — even when a free model was available on the portal. Meanwhile GET /v1/me could report a defaultModel that could not be called.

After

When the model field is omitted, the request automatically uses the portal's first callable model — as the docs describe. GET /v1/me now always reports a callable defaultModel, the same one chat will use.

Before

GET /v1/deals/:id?include=contact,company returned _included.company = null and _included.contacts as relation metadata only (sort/isPrimary/roleId) without the entity's own fields, even though the deal referenced an existing company and contacts.

After

_included.company carries the full company object, and _included.contacts carries the full contact objects (id, name, …) alongside the relation metadata. The fix applies to include across CRM entities (deals, leads, quotes, and others).

FIX-0703-13: deal search rejects an unknown filter field instead of silently returning every row

Before

GET /v1/deals and POST /v1/deals/search with an unknown filter field (a misspelled name, a field not in the schema) silently forwarded it to Bitrix24, which ignores unknown filter keys and returns the whole deal set with a 200. A client that sent a filter with a typo in the field name got neither an empty result nor a 400, but the full table — as if the filter had applied.

After

An unknown filter field is now rejected before the Bitrix24 call with a 400 and code UNKNOWN_FILTER_FIELD, listing the available fields in the message — the same behavior contacts, companies, leads, quotes, invoices, and items already have. Declared fields (including aliases such as amount), custom fields (UF_CRM_* and ufCrm*), and id work as before.

FIX-0703-14: Catalog: listing and search return a clean 400 when iblockId is missing

Before

Listing and search GET /v1/catalog-products, POST /v1/catalog-products/search, GET /v1/catalog-sections and POST /v1/catalog-sections/search without iblockId in the filter reached Bitrix24 and returned a murky 422 BITRIX_ERROR ("Field iblockId is not specified in the filter").

After

Catalog listing and search require iblockId in the filter — when it is missing they return 400 MISSING_REQUIRED_FILTER with an example and never reach Bitrix24.

Integrator impact

No change needed — correct requests (with filter[iblockId]) work as before. Only the error code and its clarity changed for requests that already failed.

FIX-0703-15: Contacts — real date fields createdTime/updatedTime instead of phantom createdAt/updatedAt

Before

GET /v1/contacts/fields advertised createdAt and updatedAt, but they never appeared in contact responses — Bitrix24 returns the dates under createdTime/updatedTime, and those are what the contact body carried. Meanwhile filter and select by createdTime (the name a client actually sees in the response) were rejected as an unknown field, while the phantom createdAt "worked" even though the field itself was never readable.

After

The schema declares the real keys createdTime and updatedTime (datetime, read-only): they appear in /fields, filter and select on them work, and the value is normalized to ISO-8601 in UTC. The phantom createdAt/updatedAt are no longer declared — filter or select on them returns 400 UNKNOWN_FILTER_FIELD.

Integrator impact

Reads are unchanged — the createdTime/updatedTime keys were already in the response body, now also normalized. If you filtered or projected contacts by createdAt/updatedAt, rename them to createdTime/updatedTime.

FIX-0703-16: Open-line config list now honors the limit parameter

Before

GET /v1/openline-configs and its search counterpart ignored limit: the underlying Bitrix24 method returns the whole set of configurations and the wrapper forwarded every row. The hasMore flag was wrong too — false even when records remained beyond the requested limit.

After

The response is clamped to limit on the wrapper side. hasMore: true when Bitrix24 returned more records than the requested limit (a next page exists), otherwise false. total is the record count in the current window.

Integrator impact

A request with limit now returns no more than limit records. Clients that relied on the whole set coming back regardless of limit will see a truncated list — use offset for the next page.

2026-07-02

NEW-0702-1: filter and sort tasks by real status (realStatus)

GET /v1/tasks, POST /v1/tasks/search and POST /v1/tasks/aggregate now accept realStatus in filter (list and search also in sort) — filtering by the task's actual stored status: 1 — new, 2 — pending, 3 — in progress, 4 — awaiting control, 5 — completed, 6 — deferred, 7 — declined. Previously filter[realStatus] was silently ignored and the request returned the whole set.

Unlike filter[status], which Bitrix24 treats as a virtual (meta) filter (values −1 overdue, −2 unviewed, −3 almost overdue) that does not match the status field value in the response, realStatus filters by the stored status. The field is read-only (change the status via status) and is used only in filter/sort — the response already exposes the task's real status in the status field.

FIX-0702-2: creating an app reuses a failed slot with the same name

Before

Repeating POST /v1/infra/servers with the same name after a failed deploy created a new app slot. Failed slots piled up and were only removed by auto-cleanup after 7 days.

After

If the key owner already has a slot with the same name on the portal in error status (or one that was created but never received a deploy), the repeated call returns that same slot: its id is preserved, the error and build log are reset, and the status goes back to provisioning — deploy into it. Slots that never received any code are now removed by auto-cleanup after 24 hours instead of 7 days (slots with a failed build are still kept for 7 days together with their build log).

Impact on integrators

No request changes are required. If your flow re-created a slot with the same name after a failure, you will start receiving the previous id instead of a new one — this is expected: deploying into the returned slot works as usual. Slots owned by other portal users and running apps are never picked up for reuse.

FIX-0702-3: read-only keys can no longer write through /v1/bots

Before

A read-only API key (accessMode: READONLY) could perform write operations through the bot endpoints (POST /v1/bots, sending and deleting messages, adding chat members, registering and deleting a bot, and others) — the call returned 200 instead of 403. Every other Bitrix24 proxy surface already blocked such writes.

After

A write through /v1/bots/* with a read-only key returns 403 with code WRITE_BLOCKED_READONLY_KEY. Read operations are unaffected, including fetching a message context (GET /v1/bots/:botId/messages/:messageId/context) and downloading a file (GET /v1/bots/:botId/files/:fileId).

Impact on integrators

If your bot integration needs to write, switch the key to read+write mode in the /keys section.

FIX-0702-4: folders include=storage now resolves

Before

GET /v1/folders/:id?include=storage (and the list form GET /v1/folders?parentId=...&include=storage) did not add _included to the response, even though GET /v1/folders/fields advertises includable: true for the storage relation.

After

The related storage now resolves: the response includes _included.storage with the storage record looked up by storageId. The relation is described in GET /v1/folders/fields.

FIX-0702-5: PAGE_BACKGROUND_WORKER: bind no longer fails with 500

Before

POST /v1/placements/bind for the PAGE_BACKGROUND_WORKER placement filled in the options.errorHandlerUrl field Bitrix24 requires only when the call went through an OAuth session. When an app bound via a developer key or on a self-hosted portal, the field was not added and Bitrix24 answered 500 (BITRIX_UNAVAILABLE, "Field errorHandlerUrl is empty"), even though other placements bound fine.

After

For PAGE_BACKGROUND_WORKER the options.errorHandlerUrl value now defaults to handler regardless of the bind path. An explicit options.errorHandlerUrl still takes precedence. The response options field now reflects the effective value (with the defaulted errorHandlerUrl).

Impact on integrators

No action required — a call that previously returned 500 now succeeds.

FIX-0702-6: POST /search reports missing required parameters with a clean error

Before

POST /v1/{entity}/search for entities whose Bitrix24 list method requires mandatory parameters did not check for them and forwarded the request to Bitrix24 as-is. A raw Bitrix24 error leaked out (BITRIX_ERROR, e.g. "Invalid value of parameter [ $id ]" or "required parameter type is not set"), whereas the equivalent GET list already returned a clear 400 MISSING_REQUIRED_PARAMS in the same case. Affected POST /v1/calendar-events/search (needs type), POST /v1/files/search (needs folderId) and POST /v1/folders/search (needs parentId).

After

POST /v1/{entity}/search validates the required parameters before calling Bitrix24 — the same guard the GET list has had for a while. A missing parameter returns 400 with code MISSING_REQUIRED_PARAMS and the list of missing fields, with no call to Bitrix24. The required parameter can be passed inside filter, and the parent parameter (folderId for files, parentId for folders) may also be passed at the top level of the request body.

NEW-0702-7: Research price in the key self-description and the required top-up in the 402 body

GET /v1/me now returns cost for every provider in the webResearch.providers[] block, mirroring the webSearch block. The field carries the research-mode price in Ꝟ (cost.research) and the currency (cost.currency), so an agent sees the deep-search price directly in the key self-description, without a separate call.

The 402 response on insufficient balance (INSUFFICIENT_BALANCE, as well as BILLING_FROZEN) on POST /v1/search and POST /v1/research now carries a required field — the amount in Ꝟ needed for the request. The existing userMessage and hint fields are unchanged.

Clients with strict additionalProperties schema validation need to account for the new response fields.

NEW-0702-8: POST /v1/triggers/fire supports invoices (SmartInvoice)

The POST /v1/triggers/fire endpoint accepts a new entityType value — invoice. Pass entityType: "invoice" and the invoice entityId (returned by GET /v1/invoices) to fire an automation trigger for a smart invoice. Existing values (deal, lead, contact, company, quote, item) keep working unchanged.

Previously firing a trigger for an invoice was impossible, and trying it through entityType="item" with entityTypeId=31 was rejected with a message that led to a dead end. Now item with a reserved entityTypeId (including 31) points to the matching entityType — for invoices, that is invoice.

NEW-0702-9: GET /v1/ai/usage returns transcribed audio duration per model

GET /v1/ai/usage now returns an audioSeconds field in the byModel[] block — the total number of audio seconds sent for transcription per model over the selected period. The field is populated for speech-to-text (Whisper) calls and is 0 for text models, where audio duration does not apply.

The field is additive — existing integrations keep working unchanged.

BC-0702-10: categories: code and isDefault marked read-only (were phantom-writable)

Old format supported until: 01.10.2026

Before

GET /v1/categories/:entityTypeId/fields advertised code and isDefault as writable (readonly: false), but writing them via crm.category.add/update was silently ignored (values not persisted, 200 returned).

After

Both fields are marked readonly: true. /fields now reports them as read-only, and an attempt to write code or isDefault returns 400 READONLY_FIELD instead of silently dropping the data.

What integrators must do

Previously, sending code/isDefault in a POST/PATCH /v1/categories/:entityTypeId body was accepted (200, values silently ignored). Now such a request returns 400 READONLY_FIELD. Remove code and isDefault from your category create/update request bodies — these fields are no longer accepted on write.

BC-0702-11: telephony-lines: crmAutoCreate normalized to boolean and now listed in /fields

Old format supported until: 01.10.2026

Before

GET /v1/telephony-lines/fields returned only number, serverName, name. The CRM auto-create flag leaked into list responses under the raw UPPER name CRM_AUTO_CREATE as a "Y"/"N" string — the only UPPER field among camelCase ones — and was absent from /fields. On write, camelCase crmAutoCreate was silently dropped.

After

The field is declared as crmAutoCreate (boolean). It now appears in /fields, comes back normalized (true/false) in list responses instead of raw "Y"/"N", and is accepted as a camelCase boolean on create/update (the raw UPPER name is still accepted on write for compatibility). Clients reading data[].CRM_AUTO_CREATE should switch to data[].crmAutoCreate (boolean).

NEW-0702-12: workgroups: aggregate operation and groupBy fields exposed

Before

POST /v1/workgroups/aggregate worked but was never advertised: the operation was missing from the machine index at /v1/guide, and groupBy returned 400 on any field (Available: .) because the aggregatable list was empty.

After

An aggregatable list is declared: membersCount (numeric sum/avg/min/max) plus categorical active, isProject, ownerId for grouping. The operation is now visible in /v1/guide and /fields, and groupBy over these fields works.

FIX-0702-13: /fields: metadata completeness for doc-templates and bookings

Before

GET /v1/doc-templates/fields omitted isDefault and productsTableVariant even though they appear in list responses. On GET /v1/bookings/fields the mandatory resourceIds and datePeriod were not flagged required, so their obligatoriness was invisible in the schema.

After

doc-templates: isDefault and productsTableVariant are declared (read-only) — /fields now matches the responses. bookings: resourceIds and datePeriod are marked required: true, so the requirement is visible in /fields.

FIX-0702-14: orders: /fields synced with responses, pseudo-key order removed, companyId filterable

Before

GET /v1/orders/fields carried a spurious pseudo-key order (an artifact of parsing sale.order.getFields) and omitted fields that actually appear in responses: companyId, clients, dateMarked, personTypeXmlId, statusXmlId, version. Because companyId was not in the schema, filtering by it failed with UNKNOWN_FILTER_FIELD.

After

/fields is now built from the schema: the order pseudo-key is gone and the six missing fields are declared (companyId — writable number; clients — read-only object returned by get; dateMarked/personTypeXmlId/statusXmlId/version — read-only). Filtering and sorting by companyId now work.

FIX-0702-15: users: limit > 50 now honored, meta.hasMore is accurate

Before

GET /v1/users?limit=500 returned only 50 records even though meta.total reported more. meta.hasMore was always false, so the documented hasMore-based pagination silently dropped everything past the first page.

After

The entity is backed by the legacy user.get method (no .list suffix), so the auto-paginator never engaged. A paginateViaStart flag was added (same as departments): for limit > 50 it now walks pages via start, and meta.hasMore reflects whether more records actually exist.

FIX-0702-16: POST /v1/batch rejects disabled write operations and forwards required list parameters

Before

A global POST /v1/batch call with action create, update or delete for an entity whose operation is disabled (for example openline-configs — writes live behind dedicated routes) went straight to Bitrix24, bypassing normalization, and could silently create or modify a record. Separately: action list or search for an entity with required method parameters (for example calendar-events — type and ownerId) returned AUTO_PAGINATION_FAILED "missing required parameter", even though a direct list request with the same parameters worked. In addition, batch list for folders and files sent the parent folder under the name parentId or folderId, which the disk.folder.getchildren method ignores, so the list silently came back for the wrong folder; and batch list for calendar-events with a leftover filter key forwarded it to Bitrix24 with no error, so the method returned the whole calendar.

After

A disabled write operation in a sub-call is rejected with ACTION_NOT_SUPPORTED before any Bitrix24 call — the same as POST /v1/{entity}/batch. Required list parameters and the method's top-level parameters are forwarded to Bitrix24 under their original names, so batch list behaves like the direct list, and when they are missing a clear MISSING_REQUIRED_PARAMS is returned instead of a raw Bitrix24 error. For folders and files the parent folder is now renamed to the id key the method expects, so batch list comes back for the right folder. For entities whose method has no filter envelope (calendar-events), a leftover filter key is now rejected with UNSUPPORTED_FILTER before any Bitrix24 call — the same as the direct list.

Integrator impact

No action needed. If a batch sub-call previously relied on a disabled write operation running, switch it to the entity's dedicated route. For batch list on entities with required parameters (calendar-events), pass type and ownerId in the sub-call params. If a calendar-events batch list used filter, drop it or move it to the top-level parameters, otherwise the sub-call returns UNSUPPORTED_FILTER.

NEW-0702-17: GET /:entity/fields now returns field label and description

The GET /v1/{entity}/fields response can now carry a human-readable short name label and an explanatory description per field — previously a field was described only by the {type, readonly} pair. This lets an AI agent or UI show a field's name and purpose without consulting the documentation. The text is returned in English. The keys were added for the following entities: Departments, Smart Processes, Storages, Folders, Files, Workgroups, Document Templates, Bookings, Calendar Events, Tasks, Requisites, Users. For Users the label resolution was additionally fixed: GET /v1/users/fields now returns the real Bitrix24 field names from user.fields instead of the technical codes. The change is additive: the new keys appear alongside the existing ones, and existing calls keep working unchanged.

FIX-0702-18: a vibe:*-only key now issues instead of failing

Before

Issuing an API key via POST /v1/keys whose every requested scope is an internal Vibecode vibe:* scope (for example only vibe:infra), on a dev-key portal (self-hosted Bitrix24 or a connected cloud portal), was rejected with 502 DEVKEY_MINT_FAILED. vibe:* scopes are never sent to Bitrix24, so the Bitrix24 webhook scope set came out empty and Bitrix24 rejected the mint, requiring at least one scope.

After

A vibe:*-only key now issues successfully. No Bitrix24 webhook is created for it — none is needed, such a key never calls the Bitrix24 REST — and the key works with Vibecode's internal features per its scopes.

Impact on integrators

No action required: the request that previously failed now returns the created key.

FIX-0702-19: /v1/storages pagination — limit over 50 returns all records, meta.hasMore is correct

Before

GET /v1/storages with limit over 50 returned at most 50 records, and meta.hasMore was always false — even when the portal held more. A client requesting ?limit=50 against 489 storages saw hasMore: false and could not tell it needed to fetch the next page. Same on POST /v1/storages/search and in /v1/batch.

After

limit over 50 goes through auto-pagination (like every other list) and returns the requested number of records, and meta.hasMore equals (offset + returned count) < meta.total even for limit of 50 or less. The change applies to GET /v1/storages, POST /v1/storages/search and the storages list in /v1/batch.

Note: the correct meta.hasMore calculation for list responses with limit of 50 or less now applies to all entities (GET /v1/{entity} and POST /v1/{entity}/search), not only storages — previously meta.hasMore was always false on this path.

FIX-0702-20: infra: Cyrillic in displayName on application create

Before

On POST /v1/infra/servers with a Cyrillic displayName, the name could be stored as a run of question marks (??????) — an encoding corruption while sending to Bitrix24.

After

The name is transmitted as UTF-8 and Cyrillic is preserved correctly.

Impact on integrators

No action required. Cyrillic names are no longer mangled.

FIX-0702-21: items: filtering by parentId relation fields

Before

Filtering by a dynamic relation field (for example parentId2 — the linked deal) on GET /v1/items/:entityTypeId and POST /v1/items/:entityTypeId/search was rejected with 400 UNKNOWN_FILTER_FIELD, even though the field is present in GET /v1/items/:entityTypeId/fields and returned in responses.

After

Fields shaped like parentId<N> are accepted in the filter and forwarded to the request as-is. Finding a smart-process item linked to a specific parent entity now works directly through the items wrapper.

Impact on integrators

No action required. Requests that previously returned 400 now succeed.

FIX-0702-22: smart processes: enumeration values and the userfield display name now persist

Before

On POST /v1/items/:entityTypeId/userfields an enumeration field (userTypeId: enumeration) was created but its value variants were not saved (the list came back empty), and a display name passed as a plain string stayed blank in the interface.

After

Value variants are accepted both as enum and as list and are saved correctly. A name passed as a plain string is automatically wrapped into a language map and fills the edit-form, list-column and list-filter labels.

Impact on integrators

No action required. The enumeration values and the name that were previously dropped silently now persist.

FIX-0702-23: req-family: address and preset-field /fields now return camelCase keys

Before

GET /v1/addresses/fields and GET /v1/requisite-presets/:presetId/fields/schema returned the field description with raw UPPER_SNAKE_CASE keys (TYPE_ID, ADDRESS_1, FIELD_NAME, IN_SHORT_LIST), even though the data of these entities (GET /v1/addresses, the preset field list) already came back in camelCase — the schema did not match the real field names in the data.

After

Both endpoints normalize the description keys to camelCase (typeId, address1, fieldName, inShortList), like the rest of V1. The inner field descriptors (type, isRequired, isReadOnly, title) are unchanged.

Impact on integrators

The /fields response keys now match the field names in the data. A client that read the camelCase names from the data gets a consistent schema; no action required.

2026-07-01

FIX-0701-1: Disk file upload accepts files larger than 1 MB

Before

POST /v1/files/upload rejected a request body larger than ~1 MB with FST_ERR_CTP_BODY_TOO_LARGE. The file is sent as base64 in the JSON body, and base64 inflates the size by roughly a third — so even a 1.1 MB file did not pass. There was no way to upload a larger file.

After

This route's body limit was raised to 70 MB — enough for a file of about 50 MB once base64 and the JSON envelope are accounted for (call recordings, typical attachments). Bitrix24 still enforces its own Disk file-size cap: exceeding it returns an error in the standard envelope. Files in the hundreds of MB need a separate upload path (multipart / presigned), which is not implemented yet.

NEW-0701-2: server app icon: SVG upload, anonymous serve, favicon

You can now set an app icon for a server. POST /v1/infra/servers/:id/icon (multipart/form-data, field file, SVG only up to 256 KB, no scripts, event handlers or external references) uploads the icon; it is served anonymously at the stable GET /api/server-icons/:id and shown in the Bitrix24 app catalog. To make it the browser-tab favicon, add <link rel="icon" type="image/svg+xml" href="<base-URL>/api/server-icons/:id"> to your app HTML at build time — after that, re-uploading the icon refreshes both the catalog and the favicon automatically. Format, requirements and procedure: App icon.

NEW-0701-3: The current-user profile reports administrator rights

GET /v1/users/me now returns a working isAdmin field: true — the user is a portal administrator, false — not, null — could not be determined (a transient failure; the profile is still returned). Use it for server-side permission checks in your backend. GET /v1/users/:id and the user list still do not expose it — the verdict is available only for the current session user.

NEW-0701-4: Extended static field contract in /v1/guide and a schema-discovery pointer in /v1/me

Each entity in the GET /v1/guide response gains a data.entities[].fieldsDetailed field — an extended static field contract: type, readonly, required, createOnly, and enum decoding (for example, the status and priority values on tasks). It is available with the X-Api-Key header alone, without a session, for mappings and code generation before a user session exists. The compact fields field is unchanged.

The GET /v1/me response for an authorization key without a user session (no Authorization: Bearer header) now carries a schemaDiscovery block — a pointer to where the static schema lives without a session (/v1/guide) and how to get live and custom fields (a Bearer session or a personal key). The GET /v1/<entity>/fields and GET /v1/userfields/* endpoints are unchanged.

BC-0701-5: categoryId in the posts response is now an array of numbers

Old format supported until: 01.01.2027

Before

GET /v1/posts returned categoryId with a type that depended on the number of a post's categories: null for none, a number (11) for one, a comma-separated string of IDs ("5,7,9") for several. A typed client with a categoryId: number | null field worked on single-category posts but broke on posts with two or more.

After

categoryId is always an array of numbers number[]: [] for none, [11] for one, [5, 7, 9] for several. The type is uniform regardless of how many categories a post has.

What integrators should do

Read categoryId as an array: post.categoryId.length instead of a null check, post.categoryId[0] for the first category. The old "number or string" branch can be removed.

FIX-0701-6: unified token and scope error codes across the Disk section

Before

Custom Disk operations — POST /v1/files/:id/moveto, copyto, POST /v1/files/upload, GET /v1/files/:id/download and the folder counterparts — returned 401 NO_TOKENS when portal tokens were missing and 403 SCOPE_MISSING when the disk scope was absent. The generated CRUD operations of the same section (list/get/create/update/delete) already returned 401 TOKEN_MISSING and 403 SCOPE_DENIED for the same conditions — so within one section a client saw two different codes for the same error.

After

All Disk operations return unified codes — 401 TOKEN_MISSING and 403 SCOPE_DENIED, matching the rest of the V1 API. The HTTP statuses (401 and 403) are unchanged.

Impact on integrators

If your code branched on the NO_TOKENS or SCOPE_MISSING strings on the moveto/copyto/upload/download operations, switch to TOKEN_MISSING / SCOPE_DENIED (or check the HTTP status). No change is needed otherwise.

FIX-0701-7: CALL_CARD placement removed from the allowed list

Before

The CALL_CARD placement code was treated as valid: POST /v1/placements/bind passed it through validation and forwarded it to Bitrix24, and GET /v1/placements/available listed it. But no Bitrix24 module registers this placement, so placement.bind failed with an internal error that the platform surfaced as INTERNAL_SERVER_ERROR.

After

CALL_CARD is removed from the allowed list: it no longer appears in GET /v1/placements/available, and POST /v1/placements/bind with it is rejected up-front with a clear VALIDATION_ERROR, without calling Bitrix24.

Impact on integrators

Binding CALL_CARD never worked (it returned an opaque 500 error), so no working integration breaks. For the call-card apps panel, use the current placements from GET /v1/placements/available.

FIX-0701-8: workday open/close/pause: the userId field is now applied

Before

The documented body field userId in POST /v1/workday/open, POST /v1/workday/close and POST /v1/workday/pause was silently ignored: the operation always ran against the key's token owner, even when another employee was passed. The response was success, but the action affected the wrong user.

After

userId is translated to the Bitrix24 USER_ID parameter, so the operation runs against the specified employee (given admin or manager rights). A non-existent userId now returns a Bitrix24 error instead of a false success. An invalid userId (not a positive integer) is rejected as 400 INVALID_PARAMS. This matches the already-working GET /v1/workday/status.

Impact on integrators

Callers that did not pass userId see no change — the operation still applies to the token owner. Callers that passed userId now get the correct action against the specified employee.

2026-06-30

NEW-0630-1: POST /v1/cowork/deploy-key — get a project deploy key from Cowork/Code

The Cowork/Code key (vibe:cowork) is data-plane only and is blocked from the infrastructure control-plane with 403 INFRA_FORBIDDEN_FOR_COWORK_KEY. The new POST /v1/cowork/deploy-key endpoint lets an agent self-serve a separate project deploy key: call it with the same Cowork key, take the top-level key field from the response (a bare object, not wrapped in data), and use it as the X-Api-Key header for deploy/provision/exec under /v1/infra/*.

The returned key carries the vibe:infra + vibe:storage scopes (no vibe:cowork), expires in 7 days, and is bound to the Cowork key's owner and portal. Each call returns a fresh key and revokes the previous project key (exactly one is active). Requires the vibe:cowork scope and an active Cowork/Code subscription; rejection codes are 403 INSUFFICIENT_SCOPE / 403 COWORK_NOT_ACTIVATED / 503 DEPLOY_KEY_DISABLED / 503 INFRA_DISABLED.

NEW-0630-2: Self-hosted placement now receives a one-time authorization code on appUrl

For an app with its own appUrl (outside Black Hole) opened as a placement, the platform now appends a one-time authorization code (?code=...) to the appUrl redirect instead of an internal gateway token. The app exchanges this code for a vibe_session via the existing POST /v1/oauth/tokenredirect_uri must exactly match the configured appUrl. Previously such apps received a non-redeemable token and could not authorize the user.

NEW-0630-3: New endpoint POST /v1/oauth/placement-session for self-hosted apps

A self-hosted app (on its own server, not on Black Hole) opened as a placement (iframe) in Bitrix24 can now exchange the Bitrix24 user token it received in the placement callback on its own handler for a vibe_session. The request POST /v1/oauth/placement-session with body { app_key, access_token, member_id, domain } (optionally refresh_token, expires_in) is server-to-server — the session token never reaches the browser. The authorization section of the docs covers both placement topologies.

FIX-0630-4: PATCH on an OAuth-app key's scopes: honest rejection instead of false access

Before

PATCH /v1/keys/:id adding a Bitrix24 scope to an OAuth-app key (vibe_app_*), and PATCH /v1/apps/:id widening an app's scopes, returned 200 and persisted the new scope set. But an OAuth app's scopes are fixed at issue time and such an edit never reaches the Bitrix24 side, so GET /v1/me then reported a scope Bitrix24 had not granted and the actual call was rejected.

After

Adding a Bitrix24 scope to an OAuth-app key or to an app is now rejected with 403 OAUTH_SCOPE_CHANGE_REQUIRES_REISSUE. Removing scopes and changing vibe:* scopes still work. To gain a new scope, issue a new authorization key with the scopes you need.

FIX-0630-5: a key with the `tasks` scope now actually unlocks Tasks methods

Before

A key issued with the tasks scope (plural — the spelling the UI picker offers) minted, on a dev-key portal, a webhook that Bitrix24 accepted but never bound to the Tasks REST methods. GET /v1/me reported tasks, yet Tasks method calls were rejected on the Bitrix24 side. The task scope (singular) worked.

After

On key issue and edit the scope set is canonicalised to the spelling Bitrix24 actually binds methods to (taskstask), so the key unlocks Tasks methods regardless of the chosen spelling. Already-issued keys are not changed retroactively — re-issue the key.

FIX-0630-6: a malformed FILES field on a timeline comment is now rejected with 400

Before

POST /v1/timelines and PATCH /v1/timelines/:id accepted the FILES field in any shape and answered 200/201. When the shape was anything other than an array of [[fileName, base64Content]] pairs — a flat array of strings or a single pair without the outer array — the comment was created but the file was attached as garbage (random name, unreadable content) or silently dropped, with no error.

After

A FILES value that is not an array of [[fileName, base64Content]] pairs is rejected before the Bitrix24 call with 400 INVALID_FILES_SHAPE and a hint about the correct shape. An empty FILES ([]) and an omitted field are still accepted. The check applies to single POST/PATCH and to batch (POST /v1/batch, POST /v1/timelines/batch).

Impact on integrators

Callers passing FILES in the documented [[fileName, base64Content]] shape are unaffected. Callers relying on other shapes now get an explicit 400 instead of a silently broken attachment, and can fix the request.

FIX-0630-7: empty bot and user fields now return null/[] instead of false/{}

Before

In bot-card responses (GET /v1/bots/:botId, POST /v1/bots, PATCH /v1/bots/:botId) the unset fields inside users[] came back as the wrong primitive: the datetimes lastActivityDate, mobileLastDate, desktopLastDate were returned as boolean false, and an empty phones list was also false. The same lastActivityDate field in GET /v1/users came back as an empty object {}. As a result new Date(lastActivityDate) silently produced the epoch and phones.map(...) threw a type error.

After

An unset datetime is now encoded uniformly as null on every path, and an empty phone list as []. A populated datetime is still an ISO string and a populated list is still an array.

Impact on integrators

No action required — the types are now correct. Code that relied on comparing empty values to false will stop matching: check the datetime against null and treat the phone list as an array.

Affected endpoints: GET /v1/bots/:botId, POST /v1/bots, PATCH /v1/bots/:botId, GET /v1/users

A new endpoint POST /v1/apps/:id/relink-oauth updates bitrixClientId and bitrixClientSecret on an existing application without deleting it. This is needed when the local OAuth application is recreated in the Bitrix24 account and its client_id changes: previously the only path was to delete the app (which broke the linked bot, openline and bindings) and create it anew.

Request body: { bitrixClientId, bitrixClientSecret } (both required). The paired key, bot and bindings are preserved. If that client_id is already linked to another application — 409 OAUTH_CLIENT_ID_IN_USE. It cannot be called with the OAuth-app's own key — 403 OAUTH_APP_KEY_CANNOT_RELINK (use a personal key or the dashboard). After relinking, reinstall the app in the account — this restores the event subscription.

NEW-0630-9: Web search: full page text, images, news mode, and research domain filters

Before

POST /v1/search accepted include_raw_content as a boolean flag, but the full text of the found pages did not reach the response. There were no parameters for news mode or for requesting images. POST /v1/research accepted include_domains and exclude_domains but silently dropped them.

After

POST /v1/search gained two new optional parameters: topic (general or news, default general) and include_images (boolean, default false). The boolean include_raw_content now actually returns the full text: each result gained a rawContent field (the full page text, size-capped). The response added top-level images (an array of objects with a url field) and ignored_filters (a string array — the passed filters the provider could not apply). These fields arrive in both the synchronous response body and the done streaming frame. The X-Search-Filters-Ignored header is kept and may now list topic and include_images. POST /v1/research now applies include_domains and exclude_domains (up to 20 each) for capable providers and reports overflow via ignored_filters in the done frame. Which capabilities a selected engine offers is returned by GET /v1/search/providers. Clients with strict schema validation via additionalProperties should account for the new response fields.

NEW-0630-10: Read AI client-call transcripts via the API

The new GET /v1/activities/:activityId/transcript endpoint returns the ready-made AI transcript of a client call by the ID of its CRM Call activity. The method only reads an existing transcript — it does not trigger generation. It requires the crm scope. When there is no transcript for the call yet, the data.transcription field is null — a normal response, not an error.

2026-06-29

FIX-0629-1: org structure node search now searches by name

Before

POST /v1/humanresources/nodes/search proxied to humanresources.node.list: the node type was set inside filter, there was no name search at all, and a top-level { "type": ..., "name": ... } body (or a request with no body) returned 400 or 500.

After

The endpoint now wraps humanresources.node.search. Two fields are required at the top level of the body — type (DEPARTMENT or TEAM) and name (a substring of the name). Optional are parentId and pagination.limit (default 50, maximum 200). It returns nodes whose name contains name, in a flat data with meta (total, hasMore). The filter, order and select fields are no longer accepted.

Integrator impact

Send { "type": "TEAM", "name": "<substring>" } at the top level instead of the former { "filter": { "type": "TEAM" } }. To enumerate all nodes of a type without name search, use GET /v1/humanresources/nodes with ?type=....

NEW-0629-2: AI quota off-peak hours schedule

Added the GET /v1/off-peak endpoint — the off-peak (Time-of-Use) discount schedule for the AI quota. The response carries the price multiplier right now (currentMultiplier), the next window when it gets cheaper (nextWindow), a 24×7 grid by hour and weekday (grid), the current grid cell (nowCell), and the schedule timezone (timezone). The discount applies to quota-metered usage only — the quota drains slower during these hours; wallet pay-per-token charges are unaffected. The optional model=<id> parameter returns a specific model's schedule instead of the platform default. Requires the vibe:ai scope. While off-peak is not enabled, the response is { "enabled": false }.

BC-0629-3: vibe-search provider slug removed

Old format supported until: 26.12.2026

Before

The provider field in POST /v1/search and POST /v1/research accepted the vibe-search slug — a separate platform engine added on 2026-06-06. It was also listed among the slugs in GET /v1/search/providers.

After

The vibe-search slug is removed. The platform search engine on every instance is bitrix-search — which upstream backs it is instance-dependent. A request with provider: "vibe-search" now returns 400 INVALID_REQUEST (the value fails validation). research support for bitrix-search is likewise instance-dependent — see GET /v1/search/providers.

What integrators should do

If your request explicitly passed provider: "vibe-search", replace it with bitrix-search or omit the provider field to use the instance default engine (shown by the defaultProvider field in GET /v1/me). The vibe-search slug was not the default engine on any production instance, so only integrations that hardcoded it are affected.

FIX-0629-4: A null field value via POST /v1/batch no longer writes the string "null" into the field

Before

In a composite POST /v1/batch, a create or update with a field value of null (for example {"entity":"deals","action":"update","entityId":123,"params":{"comments":null}}) wrote the literal string "null" into the field.

After

The field receives an empty value, which Bitrix24 interprets by field type: text is cleared, numeric becomes 0, a date is left unchanged. The literal string "null" is no longer written and no error is raised. This matches the behavior of a single PATCH /v1/{entity}/:id with null. The per-entity /v1/{entity}/batch path still skips a null field entirely (leaves the value unchanged for every type).

FIX-0629-5: Search and list with a null filter now return more than 50 rows

Before

A POST /v1/{entity}/search or GET /v1/{entity} request with a filter on an empty value (for example {"filter": {"closedDate": null}}) and a limit above 50 returned at most 50 records, even though meta.total reported the real match count and meta.hasMore was true. Auto-pagination silently stopped after the first page, so the common "read while rows equal limit" loop got an incomplete result with no error.

After

Such a request now returns up to limit records, the same as with any other filter. A null filter value is treated as "field is empty" consistently across every page of the result set.

Impact on integrators

Clients that paged manually via offset in steps of 50 to work around the truncation no longer need to — up to 5000 records can be fetched in a single call.

FIX-0629-6: port change and deploy auto-routing now work out of the box on new app servers

Before

A regular "Publish app" server booted its agent with a fixed port, so PATCH /v1/infra/servers/:id/port and the deploy auto-routing step returned 409 PORT_NOT_APPLIED (NO_SCANNER), and the public URL served the Black Hole service page while the app listened on a non-default port.

After

New regular app servers boot with port auto-detection: a service on any port is reachable through the tunnel immediately, and port change plus deploy auto-routing succeed. Agent and galaxy-host servers are unchanged.

FIX-0629-7: app install via /v1/apps returns a precise error code instead of the generic BOX_APP_INSTALL_FAILED

Before

On an OAuth-app install failure, POST /v1/apps always returned 502 BOX_APP_INSTALL_FAILED, with the full raw Bitrix24 response echoed into error.message.

After

The failure response is now classified: 403 B24_INSUFFICIENT_SCOPE (the service integration lost its rights on the portal), 410 STALE_DEVELOPER_KEY (access was changed or removed and cannot be auto-recovered), 502 RECOVERY_FAILED (transient, retryable) or 502 DEVKEY_MINT_FAILED (other). error.message no longer carries the raw Bitrix24 body — the diagnostic moves to the redacted error.details.b24Body field.

Impact

Existing "non-201 means install failed" handling keeps working unchanged. If your code branched specifically on BOX_APP_INSTALL_FAILED, add handling for the new codes above.

FIX-0629-8: date-range search no longer returns empty for wide ranges

Before

POST /v1/deals/search (and likewise for leads, contacts, companies, quotes, invoices, items) with a date filter and a lower bound (>= / >) spanning more than 14 days returned 200 with an empty data and meta.total: 0, even when records existed in that range.

After

The request returns all matching records. GET /v1/deals, narrow ranges (≤ 14 days), and the autoWindow: false parameter were unaffected.

NEW-0629-9: New error code CONNECTOR_APP_INSTALL_FORBIDDEN on app install

POST /v1/apps on a self-hosted portal now returns 403 with code CONNECTOR_APP_INSTALL_FORBIDDEN when the Bitrix24 portal administrator has forbidden the user from installing applications. The error.message field carries a clear localized explanation pointing the user to ask their portal administrator. Previously this denial surfaced as a generic 502 CONNECTOR_APP_INSTALL_FAILED with no cause; that code is still used for other install failures.

NEW-0629-10: Transfer bot ownership to another key

Added POST /v1/bots/:botId/transfer — moves a bot's ownership to another API key of the same Bitrix24 account and the same user (or an account admin). Resolves the case where a bot is orphaned after the app is recreated: the owning key is revoked and the bot's runtime stops working. Body: { "targetApiKeyId": "<id>" }. The target key must be active, in the same account, with the imbot scope. After the transfer, verify the new key's B24 binding via POST /v1/bots/:botId/reauth.

2026-06-28

FIX-0628-1: editing a key's scopes now applies them to the Bitrix24 webhook

Before

PATCH /v1/keys/:id with a scopes array saved the new set in Vibecode but, on self-hosted Bitrix24 portals, did not propagate it to the portal webhook. The webhook kept its old scope set, so a call to a freshly added scope was rejected by Bitrix24 (403) even though Vibecode already reported the key as carrying it.

After

A scopes change is now applied to the Bitrix24 webhook in the same request. If the sync cannot be performed, the key is not updated (Vibecode and Bitrix24 stay on the previous set) and the response carries an error code: INVALID_SCOPES (400) — the portal does not grant one of the requested scopes, STALE_DEVELOPER_KEY (410) or RECOVERY_FAILED (502) — the portal access key is invalid, BOX_NO_DEVELOPER_KEY (400) — the key owner has no portal access key, DEVKEY_SCOPE_SYNC_FAILED (502) — any other Bitrix24 rejection.

Impact on integrators

No action needed — scopes added via PATCH /v1/keys/:id now take effect immediately. If the response returns one of the codes above, the scope set was left unchanged: fix the cause and retry.

2026-06-27

NEW-0627-1: Catalog product properties — read and manage the property schema

Added the /v1/catalog-product-properties section — definitions of a trade catalog's custom properties (id, name, type). List, get, create, update, delete, search, and the field reference are supported. A product's list-type properties come back in /v1/catalog-products as fields named propertyNNN, where NNN is the property id. The new section maps that id to the property name and type. The filter[iblockId] filter scopes the selection to one catalog, with the id taken from /v1/catalogs. Requires the catalog scope.

2026-06-26

FIX-0626-1: catalog-prices: system fields priceScale, extraId, timestampX declared in the schema

Before

GET /v1/catalog-prices and GET /v1/catalog-prices/:id returned the priceScale, extraId, and timestampX fields, but they were not declared in the schema: they passed through without normalization (the timestampX field arrived in an offset format, e.g. 2024-06-17T16:53:24+05:30) and were absent from the GET /v1/catalog-prices/fields response.

After

The three fields are declared as read-only. They are now listed in GET /v1/catalog-prices/fields, and timestampX is normalized to ISO 8601 UTC (2024-06-17T13:53:24.000Z), consistent with the other datetime fields.

Impact on integrators

The instant in timestampX does not change — only its string representation does (UTC instead of a local offset). Clients that parse the value with a standard date parser keep working unchanged.

FIX-0626-2: Server and app source download: the signed URL no longer returns 403

Before

GET /v1/infra/servers/:id/sources/:versionId/download and GET /v1/apps/:id/sources/:versionId/download returned 200 with a signed URL, but downloading from that URL failed with 403 AccessDenied when the request used a personal key (vibe_api_*). Listing versions worked and the file was physically present in storage.

After

The signed URL is now bound to the object's actual storage location, so the download returns the archive content. The fix also covers snapshots whose storage key belongs to a different family (legacy app snapshots linked to a server).

Impact on integrators

The endpoint contract is unchanged — this restores the documented 200 + working signed URL behavior. No client-side changes are required.

NEW-0626-3: api-bearer token refresh and Gateway rejection reason

The new endpoint POST /v1/infra/servers/:id/access-tokens/:tokenId/refresh mints a fresh JWT (up to 10 minutes) for an existing api-bearer token — without creating a new record, and without consuming the active-token limit or the hourly mint limit. A long-running client (CI, AI agent) refreshes the token before jwtExpiresAt instead of minting a new one. A single revoke kills the original and all refreshed JWTs of one token.

The 401 BH_LOGIN_REQUIRED response that the Gateway returns on an app subdomain when it rejects the Authorization: Bearer header now carries a reason field with the specific cause: expired, signature, subdomain, type, revoked, malformed, or invalid. The field is additive — existing clients ignore it.

2026-06-25

FIX-0625-1: neutral provider, plan and region identifiers in the infrastructure API

Before

On the international (.com) surface, GET /v1/infra/providers, GET /v1/infra/providers/:id/plans and GET /v1/infra/providers/:id/regions returned provider, plan, disk-type and region identifiers in the underlying infrastructure's raw form rather than in the neutral brand namespace.

After

The same fields now use the neutral Bitrix Cloud namespace, matching the other segment: provider bitrix-cloud, plans bc-small/bc-medium/bc-large/bc-xlarge, diskType: "network-ssd", regions bc-eu-central/bc-eu-west/bc-us-east/bc-us-west/bc-ap-southeast. The name and country fields stay human-readable (for example "Frankfurt (EU Central)", DE). The same values are returned in the provider/plan/region fields of GET /v1/infra/servers and GET /v1/infra/servers/:id.

Integrator impact

The standard flow needs no changes: an identifier read from the catalog is still passed to POST /v1/infra/servers as-is. Server creation accepts both the new ids (bitrix-cloud/bc-small/bc-eu-central) and the ids from the previous catalog, so existing integrations keep working. Only update code that compares response identifiers against hard-coded strings from the previous provider, plan, or region catalog.

FIX-0625-2: offset inside /v1/batch sub-calls now pages through results

Before

In POST /v1/batch, list and search sub-calls silently ignored offset in params: Bitrix24 received an unknown offset key instead of start, so every page returned the same first set of records. Three contacts.search sub-calls with offset 0, 50 and 100 all returned the identical first page.

After

offset in a list/search sub-call is mapped to Bitrix24's start (matching the single POST /v1/{entity}/search endpoint). The same three sub-calls now return three distinct, non-overlapping pages. Single-endpoint behavior is unchanged.

FIX-0625-3: Files and folders: deletedBy on non-deleted objects is now null

Before

GET /v1/files/:id, GET /v1/files and the folder endpoints returned deletedBy: 0 for a non-deleted object, although the field is declared as number | null with "null — the object is not deleted". The sibling field deletedAt correctly returned null, so two fields with the same contract behaved differently, and a deletedBy !== null check wrongly treated every active object as deleted.

After

deletedBy is normalized to null for non-deleted objects (Bitrix24 stores zero in the DELETED_BY column to mean "no user"; user id 0 does not exist). For deleted objects the field still carries the id of the deleting user.

Impact on integrators

The behavior now matches the documented number | null contract. Clients that checked deletedBy === null / deletedBy !== null now get the correct result for active objects.

Read access to Knowledge Base 2.0: list the knowledge bases you can access (cursor pagination), get a knowledge base and a document by id (the document includes its Markdown content), the document tree of a knowledge base, and full-text search across documents. Scope note.

Affected endpoints: GET /v1/note/collections (list), GET /v1/note/collections/:id (one base), GET /v1/note/collections/:collectionId/documents (tree), GET /v1/note/documents/:id (document with Markdown), GET /v1/note/documents/search (search by query).

FIX-0625-5: Knowledge Base 2.0 methods (create, update, file upload) now work

Before

Creating and updating knowledge bases and documents (POST /v1/note/collections, PATCH /v1/note/collections/:id, POST /v1/note/documents, PATCH /v1/note/documents/:id) returned 400 with a Bitrix24 validation error, and attachment upload (POST /v1/note/documents/:documentId/files) stored the file but did not return its id in data.id.

After

The methods work: create and update return 200, and create responses for knowledge bases, documents, and files carry the id in data.id. Archive, delete, and file retrieval already worked.

FIX-0625-6: /v1/me: storage supportedVisibilities are now uppercase

Before

GET /v1/me returned supportedVisibilities: ["private","public"] (lowercase) in the storage block, but the upload endpoints accept only PRIVATE/PUBLIC (uppercase). An agent copying the value from the manifest hit STORAGE_INVALID_VISIBILITY.

After

supportedVisibilities is returned as ["PRIVATE","PUBLIC"] — exactly the values the visibility upload parameter accepts.

Impact on integrators

If a client took the visibility value from /v1/me and uppercased it itself, nothing changes. If it passed the value as-is, uploads now succeed without an error.

2026-06-24

FIX-0624-1: galaxy app logs return container output

Before

GET /v1/infra/servers/:id/logs for a galaxy app (kind=GALAXY_APP) returned only the host system journal (journalctl), not the app container's own logs — you could not see the stdout/stderr of a crashed app.

After

For a galaxy app the endpoint reads the container's stdout/stderr (docker logs). The read is read-only: if the galaxy host is asleep or unreachable, the response is an empty data.logs plus data.hint, and the host is not woken. The since parameter for galaxy apps accepts only a duration (10m) or an RFC3339 timestamp — the human-readable journalctl forms ("1 hour ago") are allowed only for Black Hole servers.

NEW-0624-2: GALAXY_APP_START_FAILED deploy error code

POST /v1/infra/servers/:id/deploy for a galaxy app returns 502 GALAXY_APP_START_FAILED when the app builds successfully but crashes or enters an OOM restart loop right after start. This is a distinct code from GALAXY_APP_BUILD_FAILED (a build error): it shows the build succeeded and the problem is at runtime (for example, exceeding the memory limit). The tail of the container logs is delivered in the buildLog field.

FIX-0624-3: blocking server wake window raised to ~5 minutes

Before

A blocking wake — POST /v1/infra/servers/:id/wake with ?wait=true and the auto-wake of a sleeping server on POST /v1/infra/servers/:id/deploy — waited for readiness (status RUNNING plus a connected tunnel) for up to about 3 minutes, then returned 504 WAKE_TIMEOUT.

After

The main wait phase is raised from ~3 to ~5 minutes, and including the reboot phase the full ceiling before 504 is about 6.5 minutes. A deeply cold host (for example, a galaxy that has slept for several days) gets enough time to boot and connect instead of hitting a false timeout. The error code, response shape, and the proxy-side ceiling are unchanged.

Impact on integrators

If your client sets its own timeout for these calls, budget about 6.5 minutes instead of 3. Everything else is unchanged — you do not need to rewrite the integration.

NEW-0624-4: placement and graduateFrom parameters on server create

POST /v1/infra/servers gained two optional parameters. placementauto (default, unchanged behavior) or dedicated: on a portal with the galaxies-only placement model, the value dedicated creates a standalone virtual machine instead of a Galaxy application, passing the same checks as a normal server create — the serverCreation policy and the per-user server quota. graduateFrom takes the identifier of your Galaxy application (kind=GALAXY_APP): after the dedicated server is created, that application is deleted. graduateFrom is owner-scoped — a foreign or non-Galaxy identifier returns 404 and deletes nothing.

This is additive: without placement, or with placement: "auto", the request behaves exactly as before.

In addition, when a Galaxy application fails with OOM, the response of POST /v1/infra/servers/:id/deploy with the code 502 GALAXY_APP_START_FAILED now carries a structured hint error.hint with recoveryAction: "graduate-to-dedicated-vm" — how to re-create the application on a dedicated server via placement: "dedicated" and graduateFrom. The hint is added only when the failure cause is OOM (exceeding the container memory limit), not a plain crash.

FIX-0624-5: server creation with code in source.content accepts archives up to 500 MB

Before

POST /v1/infra/servers with an inline archive in source.content (one-shot Galaxy app creation) returned 413 FST_ERR_CTP_BODY_TOO_LARGE for archives above ~750 KB, even though the documented limit for source.content is 500 MB per request body. The route inherited the global 1 MB body limit.

After

The route accepts a request body up to 500 MB — matching POST /v1/infra/servers/:id/deploy and POST /v1/infra/servers/:id/upload. The documented limit now actually applies.

NEW-0624-6: group requisites by tax IDs (INN/OGRN/KPP) in aggregate

POST /v1/requisites/aggregate now accepts groupBy on the requisite's string identifiers: rqInn, rqKpp, rqOgrn, rqOgrnip, rqOkpo, rqVatId, rqResidenceCountry, rqCompanyName, plus presetId, entityTypeId, active. Previously grouping by these fields returned 400 INVALID_PARAMS with an empty list of available fields.

Grouping by rqInn is the fastest way to find duplicate requisites in a single call: groups with count > 1 hold the repeated values. Existing calls (groupBy on entityTypeId/presetId) keep working unchanged. Numeric functions (sum/avg/min/max) remain unavailable on these string fields — they are for grouping only.

FIX-0624-7: a sleeping server's availableActions includes wake/start; repair-status reports `running` immediately

Before

For a sleeping server with no tunnel (SLEEPING + blackholeStatus: DISCONNECTED — the normal state of a stopped server) the availableActions field in the 422 SERVER_WRONG_STATE response and in GET /v1/me (infra.unhealthyServers) listed only ["repair","delete"] — no obvious way to bring the server back. Separately: right after POST /v1/infra/servers/:id/repair, a repair-status poll in the first milliseconds could return {status:"idle"}, and the poll loop exited prematurely.

After

availableActions for any sleeping server now contains ["wake","start","repair","delete"] — both actions are genuinely accepted by the /wake and /start endpoints. And repair-status sets running synchronously when the repair starts, so the very first poll sees running, not idle. Existing calls keep working unchanged.

FIX-0624-8: stage-history?entityType=invoice now returns smart-invoice history (31)

Before

GET /v1/stage-history?entityType=invoice returned the history of the deprecated old invoice (entityTypeId 5, status-based: statusId/statusSemanticId). The current smart invoice (31) was reachable only under entityType=new-invoice. A client working with invoices via /v1/invoices (type 31) who queried history under invoice got a foreign, deprecated type.

After

entityType=invoice returns the current smart invoice's history (entityTypeId 31, stage-based: stageId/stageSemanticId/categoryId) — consistent with /v1/invoices. The new-invoice key is kept as an alias for 31 for backward compatibility, so nothing needs to change.

NEW-0624-9: bitrix/embeddings text embeddings endpoint

An OpenAI-compatible POST /v1/embeddings endpoint converts text into vector representations (embeddings) for semantic search, clustering, deduplication, and finding similar CRM records. The bitrix/embeddings model is free and platform-provided — no provider key of your own is required. The input field accepts a string or an array of strings, and the response is returned in the raw OpenAI format: an object field set to list, a data array of objects shaped like { object: "embedding", embedding, index }, and a usage block. The optional encoding_format (float or base64) and dimensions parameters are supported. The list of available models and their capabilities is at GET /v1/models, and the embeddings model has the embeddings capability set.

FIX-0624-10: calendar event field types now match real responses

Before

GET /v1/calendar-events/fields declared rrule as string and dateCreate and updatedAt as datetime, while on read rrule comes back as an object and dateCreate and updatedAt come back as a string in the portal's regional format (not ISO 8601). The schema listed an ownerType field that responses never return. The rrule object carried internal keys ~UNTIL and UNTIL_TS, and recurring-event list items carried an internal RINDEX key.

After

/fields declares rrule as object and dateCreate and updatedAt as string. The phantom ownerType field is removed from the schema. The internal keys ~UNTIL, UNTIL_TS, and RINDEX no longer reach responses.

Impact on integrators

Documented fields did not change — a client that read only them keeps working. Use from and to (ISO 8601) for an absolute timestamp. Do not parse dateCreate and updatedAt with a fixed parser — their format depends on the portal's regional settings.

NEW-0624-11: provisionReason field in the server responses

GET /v1/infra/servers and GET /v1/infra/servers/:id now return a provisionReason field with the value oom, crash or null — a structured reason for a galaxy app failure. Previously it was exposed only on the cabinet session routes, so the Vibecode API had to parse the free-text provisionError. The oom value signals to graduate the app to a dedicated server: create one with placement set to dedicated and graduateFrom. The field is optional and additive — existing integrations keep working unchanged.

FIX-0624-12: the graduation signal fires for any galaxy app memory shortage

Before

A Galaxy app that ran out of container memory — both one that hit the limit and kept restarting at the edge and one that exhausts memory right at startup (for example, loading a big model) — was classified as an ordinary crash: GET /v1/infra/servers/:id returned provisionReason crash, and the POST /v1/infra/servers/:id/deploy 502 GALAXY_APP_START_FAILED response carried no graduation hint. Conversely, a non-OOM crash-loop (an unhandled exception) could be wrongly marked oom.

After

A real memory shortage in any shape — an instant boot-OOM or a grow-into-the-limit loop — now reliably yields provisionReason oom and an error.hint with recoveryAction graduate-to-dedicated-vm. Build errors and apps that never started (a broken start command) stay crash with no graduation.

Impact on integrators

No action needed: the field value and the hint now reflect the memory shortage more accurately. An agent can reliably detect provisionReason oom for any memory exhaustion and graduate the app to a dedicated server.

2026-06-23

NEW-0623-10: push-delivery setup hint in event-subscription errors

The 400 NOT_OAUTH_APP and 400 NO_USER_TOKEN errors of POST /v1/infra/servers/:id/event-subscriptions now include an error.hint field — a text instruction on how to get a server backed by an authorization key (vibe_app_) for push delivery: create an authorization key via POST /v1/apps, authorize the app on the portal, then create a new server under that key. There is no separate "migration" of an existing server from a regular key. The field is additive — existing clients are unaffected.

FIX-0623-1: bizproc activity list

Before

GET /v1/bizproc-activities returned each activity code as an object with per-character numeric keys — for example {"0":"D","1":"i", …} instead of the string "DiskRead". The Array.includes(code) check did not work.

After

The endpoint returns activity codes as an array of strings, as documented.

FIX-0623-2: duplicate-search response keys in camelCase

Before

POST /v1/duplicates/find returned the data object keys in upper case (LEAD, CONTACT, COMPANY), unlike the rest of the API which uses camelCase.

After

Keys come back in camelCase (lead, contact, company); the values (arrays of ids) are unchanged.

FIX-0623-3: Scrum epic files field as an array of ids

Before

GET /v1/scrum/epics/:id returned the files field as a raw Bitrix24 UF object (with VALUE_RAW, USER_TYPE_ID and other internal metadata).

After

files is an array of attachment ids ([417]) or an empty array, consistent with the rest of the API.

BC-0623-4: API key creation is admin-only

Old format supported until: not applicable, the restriction takes effect immediately

Before

Any portal user could create an API key (POST /v1/keys).

After

Key creation is available only to portal administrators, others are rejected.

What integrators should do

Create keys under an account with Bitrix24 administrator rights.

FIX-0623-5: Retry-After header on rate limiting

Before

On a 429 (rate limit exceeded) response the Retry-After header was not returned, so the integrator did not know when to retry.

After

The 429 response carries Retry-After with the interval in seconds. Use it as the pause before retrying.

FIX-0623-6: a deleted server returns 404 again

Before

GET /v1/infra/servers/:id returned 200 with a full body and status: "deleted" for a soft-deleted server, although the documentation promises 404. A client polling the endpoint and expecting 404 as deletion confirmation never received it.

After

The endpoint returns 404 NOT_FOUND for a deleted server — the same as the list and delete, and as described in the documentation.

Impact on integrators

If your code relied on 200 with status: "deleted", switch to checking for 404 (or for the server's absence from the list) as the deletion signal.

NEW-0623-7: Universal Lists — full REST API

A new Lists section (scope lists): programmatic access to Bitrix24 Universal Lists — the lists themselves, their fields, sections, and elements. 24 endpoints under /v1/lists over the lists.* methods.

A list is addressed by infoblock type (iblockTypeIdlists, lists_socnet, or bitrix_processes, default lists) and an identifier: a numeric path segment is treated as IBLOCK_ID, a non-numeric one as the symbolic IBLOCK_CODE. Fields, sections, and elements are available under nested paths.

If the Universal Lists module is not enabled on the portal, the call returns 409 LISTS_MODULE_NOT_ENABLED — a sign the module is off, not an integration error.

Affected endpoints: /v1/lists, /v1/lists/:iblockId, /v1/lists/:iblockId/fields, /v1/lists/:iblockId/sections, /v1/lists/:iblockId/elements

FIX-0623-8: smart-process relation isChildrenListEnabled flag

Before

The nested relation flag isChildrenListEnabled was accepted only as true/false. A Y/N value, like the other smart-process flags, was silently saved as disabled.

After

POST /v1/smart-processes and PATCH /v1/smart-processes/:entityTypeId coerce Y/N (and 1/0, yes/no) to true/false for isChildrenListEnabled in relations.

FIX-0623-9: deal custom field filter and select

Before

When filtering and selecting deal custom (UF) fields in the UF_CRM_* form, the field was rejected with UNKNOWN_FILTER_FIELD in the filter and silently dropped from select.

After

Deal custom fields are given in camelCase (ufCrmCheckOut) and work unchanged in filter and select.

Affected endpoints: GET /v1/deals, POST /v1/deals/search

2026-06-22

NEW-0622-1: deal-to-contacts relation

Manage a deal's contact set: read, add, replace the whole set, delete. PUT replaces the entire set at once. Scope crm.

Affected endpoints: GET/POST/PUT/DELETE /v1/deals/:id/contactsDeal contacts

BC-0622-2: model list contains only GA models

Old format supported until: not applicable, experimental models were not part of the stable contract

Before

GET /v1/models and the model list in /v1/me included experimental non-GA models.

After

The public list contains only GA models. Experimental ones are excluded from the list and rejected on call.

What integrators should do

Pick a model from the current GET /v1/models response, do not hardcode experimental model ids.

FIX-0622-3: department auto-pagination

Before

GET /v1/departments returned only the first page when limit > 50.

After

Auto-pagination collects all departments into a single response.

2026-06-19

FIX-0619-1: document creation

Before

POST /v1/documents returned 422 and did not create the document.

After

The endpoint creates a document from a template and returns the record.

FIX-0619-2: warehouse auto-pagination

Before

GET /v1/warehouses and warehouse stock returned only the first page when limit > 50.

After

Auto-pagination collects every record into a single response.

Affected endpoints: GET /v1/warehouses, GET /v1/warehouses/:id/stock

FIX-0619-3: list-type custom field values preserved

Before

When creating and updating a "list" (enum) custom field, the list values were lost.

After

List values are preserved on create and update.

Affected endpoints: create, update a custom field

FIX-0619-4: partial update of a basket item

Before

PATCH /v1/basket-items/:id did not perform a partial update of the item.

After

Partial update works, the quantity field is required in the body.

FIX-0619-5: open-channel config filter and sort

Before

For open-channel configs, filter and sort did not work for all fields, and values were not normalized on write.

After

Filter and sort respect the field schema, boolean values are coerced to the Bitrix24 format (Y/N) on write.

2026-06-18

NEW-0618-1: grouping in deal aggregation

POST /v1/deals/aggregate accepts groupBy: "stageSemanticId" — a breakdown by stage semantics (in progress, won, lost) for funnel analytics.

NEW-0618-2: pagination and filter for stage history

GET /v1/stage-history supports pagination (meta.total, meta.hasMore) and a filter by entity type entityTypeId.

FIX-0618-3: task time tracking does not reassign the author

Before

PATCH /v1/tasks/:taskId/time/:id accepted a userId field, but Bitrix24 does not reassign the record author — the value was silently ignored.

After

The userId field is rejected with 400 — the author of a time-tracking record cannot be changed.

Impact on integrators

Do not pass userId when updating a time-tracking record.

FIX-0618-4: calendar event time zone

Before

PATCH /v1/calendar-events/:id could save the time in the Bitrix24 user's time zone rather than the event's own.

After

The event time zone is preserved on update.

2026-06-17

NEW-0617-1: Knowledge Base 2.0 (note.*)

Knowledge base collections, documents, and attachments: create, update, archive, and delete knowledge bases and documents, plus attachment upload. Scope note.

Affected endpoints (write methods): POST /v1/note/collections, POST /v1/note/documents, POST /v1/note/documents/:documentId/files, plus the paired PATCH and DELETE. Read methods are added in a separate entry.

2026-06-16

NEW-0616-1: AI follow-up for completed calls

AI follow-up for completed calls. Scope call.

Rolling out — the methods ship in Bitrix24 update call 26.600.0 and are not yet available on every portal. Until the update reaches a portal, the method returns 422 METHOD_NOT_YET_AVAILABLE with the target version in the response — this is a sign of the rollout, not an integration error.

Affected endpoints: POST /v1/calls/followups/list, GET /v1/calls/followups/:callId

FIX-0616-2: transcription response format

Before

POST /v1/audio/transcriptions always returned a JSON object, even with response_format=text, srt, or vtt.

After

text, srt, vtt return the raw body as text/plain, SubRip, or WebVTT. json and verbose_json return a JSON object.

2026-06-12

BC-0612-1: order payed field is read-only

Old format supported until: not applicable, the field is now read-only

Before

payed was accepted in the order create and update body.

After

payed is read-only — it is rejected on write.

What integrators should do

Remove payed from the body of POST /v1/orders and PATCH /v1/orders/:id.

Affected endpoints: POST /v1/orders, PATCH /v1/orders/:id

2026-06-11

NEW-0611-1: Scrum API

Epics, linking tasks to epics, and reading the task chat. Scope tasks.

Affected endpoints: /v1/scrum/epics, /v1/scrum/epics/:id, /v1/scrum/tasks/:taskId — the Scrum section.

NEW-0611-2: call rating on completion

POST /v1/calls/:callId/finish accepts a rating for the completed call and forwards it to Bitrix24.

See also