# API changes: September 20, 2026

[← Changelog](/docs/changelog) · [September 2026](/docs/changelog/2026-09)

### FIX-0920-1: the OpenAPI specification and the API reference name the required filter keys and count over `"*"`

**Before**

Nine entities do not run a read without a required filter key: calendar events (`type`, `ownerId`), calendar sections, Drive files and folders, timeline comments (`entityType`, `entityId`), catalog products and sections (`iblockId`), product list-property values (`propertyId`) and org-structure nodes (`type`). Without the key the operation answers `400 MISSING_REQUIRED_PARAMS` or `400 MISSING_REQUIRED_FILTER`. The machine specification `GET /v1/openapi.json` did not say so: on the `GET` list of all nine entities, on the `POST /search` of six of them and on [POST /v1/catalog-products/aggregate](/docs/entities/catalog-products/aggregate) the filter was described as optional. The reference examples led into the same refusal: search printed an empty `filter`, the list a call without parameters. The aggregation example of over twenty entities counted `count` over a field, although the operation accepts `count` over `"*"` only and answers `400 INVALID_PARAMS`, and the list of aggregation fields in the specification did not contain `"*"`. The per-entity batch example `POST /v1/{entity}/batch` passed `limit` next to `params`, where the sub-call does not read it.

**After**

The specification declares the required keys where the operation requires them. On search and aggregation they are part of the `filter` description, and search of files and folders also takes the key at the body top level. The `folderId` / `parentId` value is described as a non-empty scalar. On the list each key is described as its own query parameter and can be sent as `?type=…` or as `filter[type]=…`, as before. The machine-readable list of keys is added as the `x-required-filter-keys` extension, and whether a batch call lifts the required parameters of a sub-call as the `x-batch-list-lifts-params` extension. Each key is named together with its refusal code. The list of aggregation fields contains `"*"`. A batch sub-call is described by its `params` field. The reference examples pass the required keys, aggregation counts over `"*"`, the batch example puts the parameters into `params`. Where a batch `list` cannot receive the required parameters, the example shows `get`, and `create` for calendar sections. The responses of the operations have not changed.

**Impact on integrators**

Working requests are not affected: the operations refused without these keys before. A client generated from the specification gets a required `filter` on search and aggregation of these entities once regenerated. The exception is search of files and folders: there the key is required, in `filter` or at the body top level.

### BC-0920-2: updating or deleting a missing employee field answers 404

> Old format supported until: not provided

**Before**

[PATCH /v1/userfields/users/:id](/docs/userfields/users/update) and [DELETE /v1/userfields/users/:id](/docs/userfields/users/delete) answered `422 BITRIX_ERROR` with the message `Access denied.` for an id the Bitrix24 account does not have — the same answer a genuine permission refusal produces. Deleting an already deleted field answered the same way, while [GET /v1/userfields/users/:id](/docs/userfields/users/get) answered `404` for that id. An id written with leading zeros was not resolved by `GET` either: `007` answered `404`, although updating and deleting the same field by `007` worked.

**After**

The platform checks whether the field exists before updating or deleting it. No field — the answer is `404 ENTITY_NOT_FOUND`, and no write request reaches Bitrix24. The same code comes back for a repeated delete and for the id of a field that is not an employee field. The permission refusal is unchanged: a key whose owner is not a Bitrix24 account administrator still gets `422 BITRIX_ERROR` with the message `Access denied.`. In addition, `GET /v1/userfields/users/:id` now resolves an id written with leading zeros: `007` reads as `7`, the way update and delete have always treated it.

**What integrators should do**

Move the "field is missing" branch from `422 BITRIX_ERROR` to `404 ENTITY_NOT_FOUND`, exactly as it is already written for CRM fields. A repeated `DELETE` answering `404` means the field is already gone, not an error. Keep the `422 BITRIX_ERROR` branch with the message `Access denied.` — it now means either insufficient rights or a race where the field was removed between the check and the write. Update and delete each take one extra request to Bitrix24.

### FIX-0920-3: two simultaneous server creations for one application now yield one machine

**Before**

Two `POST /v1/infra/servers` calls arriving at nearly the same moment with the key of one application that had no server yet both went through: each saw an application without a server, and each created a billable machine. One of them was bound to the application card; the other kept running and kept being charged while showing up neither on the card nor in reuse — it could only be found in `GET /v1/infra/servers` and had to be deleted by hand.

**After**

This is about an ordinary create — a call WITHOUT `placement: "dedicated"`. The application's seat for its server is taken indivisibly on the Vibecode platform, at the same moment the server row is written to the database and before the cloud is called at all. This covers applications calling the API with an authorization key too: their card used to receive its server only at deploy time, and until then every further create raised one more machine — now the server lands on the card immediately and a repeat call returns that same server. A second machine is therefore no longer born: the call that arrives late for the free seat stops before anything is paid for, and answers `201` with the server the first call created plus `reused: true`, so there is nothing to retry. If that late call carried source, the response itself says what became of it, and that depends on whether there is anything to damage. When the server handed back is being deployed onto by the first call right now — and always for an ordinary server, onto which a create never deploys source at all — a second deploy on top of somebody else's is not started: the response carries `sourceIgnored: true`, so watch the server's state and deploy your own source in a separate call if it differed. When there is nothing to damage (the application's server has never been deployed onto), the late call's source is deployed as usual and the response carries `deploying: true`. Read those fields instead of assuming the outcome. When converging on that server is not possible, the answer is `409` with the new code `APPLICATION_SERVER_SLOT_TAKEN` and no machine is created for it either. That answer means the application has no server to hand back right now: its card was deleted meanwhile, or the machine sitting on it cannot be handed back — it is dead, or it is a shared host. Repeating the call in either case creates a NEW server rather than returning the old one, so read `GET /v1/applications` before repeating.

The protection also works before the application has a card: the first call raises the server, the second gets `409` with the code `APPLICATION_FIRST_SERVER_IN_PROGRESS`, and the retry returns the existing server as soon as the card appears. The two refusal codes are deliberately different, because they call for different actions: after `APPLICATION_FIRST_SERVER_IN_PROGRESS` a retry returns the server, after `APPLICATION_SERVER_SLOT_TAKEN` it creates a new one. Branch on the code — the message text is not meant for that. Two boundaries remain, both deliberate. The first is a call with `placement: "dedicated"`: there an additional machine is exactly what you are asking for, and refusing it would be wrong. The second is an account with the Applications section switched off: no cards are created there at all, both machines stay visible in the server list, and neither is lost. Keep your own protection against repeated calls on those two paths. Successful responses of single creations are unchanged.

### BC-0920-4: the clientId field is removed from the Cowork employee list response

> Old format supported until: not provided

**Before**

The `portal` block of the `GET /v1/platform/cowork/members` response carried a `clientId` field. For cloud accounts it was always empty: the column it was read from is not filled for any cloud account.

**After**

The `portal` block has no `clientId` field. The account is still identified by `portalNetworkId` and `portalDomain`; the other response fields are unchanged.

**What integrators should do**

Nothing, if the field was never read. If your client expects a `clientId` key in the `portal` block, drop that check: its value was empty anyway. Address the account through `portalNetworkId`.

### FIX-0920-5: server status stops reporting "connected" while the tunnel is dead

**Before**

When a server's connection to the platform dropped silently, the server status could stay `CONNECTED` forever: the server card showed the server as running while calls to the application external API no longer arrived. The caller got `503 APP_API_UNAVAILABLE`, but the state never corrected itself — the server stayed that way until the connection happened to come back, and the status could not tell "the application is quiet right now" apart from "there is no connection at all".

**After**

On a refusal caused by a dropped connection, the platform checks the actual list of live connections and, when the connection is genuinely gone, clears the incorrect `CONNECTED` — the server status becomes `DISCONNECTED` and the server enters the regular recovery path. The check runs only on that refusal branch and only on a confirmed absence: while the list of live connections is unavailable, or the connection is present in it, the status is left alone. The answer to the caller is unchanged — `503 APP_API_UNAVAILABLE`, with no new error codes.

### FIX-0920-6: the OpenAPI schema declares the scope refusal on entity read operations

**Before**

Entity read operations — list, read by id, `fields`, `search`, `aggregate`, related-record and product-row reads — declared no `403` response in the OpenAPI schema, although the Vibecode API answers `403` with code `SCOPE_DENIED` when the key lacks the scope the operation requires. The requirement itself was published all along: the `x-required-scope` extension and the "Requires scope" sentence in the operation description. A client generated from the schema had no error model for the most ordinary refusal and met it as an unexpected server answer.

**After**

Every entity read operation declares `403` with code `SCOPE_DENIED` and names the codes that reach the same status from the shared key gate and from Bitrix24 itself. The `fields` operation deliberately names no Bitrix24 code there, for two reasons. Where the entity has a live field method, that fetch is best-effort and its failure arrives as a successful answer carrying the `fields_partial` warning. Where it has none, the field set is served from the declared contract and no Bitrix24 call happens at all — as the operation's own description states.

**Impact on integrators**

Nothing to do: endpoint behaviour is unchanged and the codes and statuses stay as they were. Regenerate your client from the schema to get a typed branch for the scope refusal.

### BC-0920-7: batch writes and import no longer answer with success when Bitrix24 did not apply the stage or the manual amount

> Old format supported until: not provided

**Before**

The single `PATCH /v1/deals/{id}`, `POST /v1/deals/{id}/move` (and the lead counterparts) already answered `422 STAGE_NOT_APPLIED`, and `POST` / `PATCH /v1/items/{entityTypeId}` answered `422 AMOUNT_NOT_APPLIED`, when Bitrix24 accepted the write but did not apply the stage, the pipeline, or an explicitly requested manual amount mode. The same writes sent through `POST /v1/{entity}/batch`, the global `POST /v1/batch` and `POST /v1/{entity}/import` answered `success: true` per item: the deal stayed on its previous stage (on import — landed on the default stage), the smart-process item amount became zero, and the caller saw success.

**After**

In `POST /v1/{entity}/batch` an item whose stage, pipeline or manual amount Bitrix24 did not apply comes back with `success: false`, the code `STAGE_NOT_APPLIED` or `AMOUNT_NOT_APPLIED` in `error`, an explanation in `message` and `details.unappliedFields` / `details.currentValues`; such an item keeps its `id` — the record was already created or updated, do not repeat it. In the global `POST /v1/batch` such a sub-call moves from `data.results` to `data.errors` under its `id`, with the same `code` / `message` / `details`, and the record Bitrix24 returned in the answer to the sub-call arrives in `data.errors.<id>.data`. In `POST /v1/{entity}/import` the result row gets the same fields and keeps its `id` as well. For both batch doors no additional Bitrix24 calls were added: the record compared is the one Bitrix24 already returns in the answer to each sub-call. After all chunks, import re-reads the created records once per page of fifty, and only those that carried a stage, a pipeline or an explicit manual amount mode — every other import costs exactly what it did before; import runs no automation rules, so the re-read record is the outcome of the import itself. Single create `POST /v1/deals` and `POST /v1/leads` does not verify the stage and answers as before: Bitrix24 automation does run there, and a robot «on creation — change stage» would produce a false refusal. A lead Bitrix24 itself converted in simple CRM mode on creation is not counted as an unapplied stage; on an update of an already closed lead an unapplied stage is refused, as in the single `PATCH`. Writes with valid values answer as before; an amount without an explicit `isManualOpportunity: true` is still not checked.

### FIX-0920-8: the previous turn's reasoning reaches the model again

**Before**

In a tool dialogue the previous assistant turn's reasoning, returned in `messages[].reasoning_content`, did not reach the model: the platform passed it to the cluster under a name the model template does not read. The model lost the thread between tool calls, and the reasoning text did not count towards input tokens.

**After**

The platform passes the reasoning under the name the model template reads, so in a tool dialogue the model sees its previous reasoning again. The request field is unchanged: return the reasoning in `messages[].reasoning_content`, as described in [function calling](/docs/ai/chat/tools). The reasoning text now counts towards input tokens, so `usage.prompt_tokens` is higher for such requests.

**Affected endpoints:** [POST /v1/chat/completions](/docs/ai/chat/completions).
