# API changes: September 11, 2026

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

### FIX-0911-1: the OpenAPI spec's scope slice no longer pulls in unrelated hand-written sections

**Before**

The `?scope=<scope>` parameter on [GET /v1/openapi.json](/docs/cli) sliced only the generated entities. Hand-written sections — bots, feedback, infrastructure, AI, platform files, search, placements — stayed in EVERY slice in full, regardless of the requested scope. The `?scope=tasks` slice weighed 725,100 bytes and contained 284 paths, including the full `/v1/apps`, `/v1/placements`, `/v1/bots`, `/v1/feedback` sections.

**After**

The slice keeps the entities of the requested scope, the hand-written routes of the same section, and the service endpoints `/v1/me`, `/v1/guide`, `/v1/batch` — everything else is cut. `?scope=tasks` now returns 34 paths instead of 284. Hand-written sections have their own `?scope=` values: `imbot` — bots, `vibe:feedback` — feedback, `vibe:infra` — infrastructure, `vibe:ai` — AI, `vibe:storage` — platform files, `vibe:search` — search, `placement` — placements. Anyone who used to read a foreign section from a foreign slice — bots from `?scope=crm`, for example — now gets it with a separate call under that section's own scope: several sections at once need one GET per scope, a comma-separated list still does not union them. The full specification with no `?scope=`, and the response to garbage, an empty, or a value that does not collapse to one scope, are unchanged. The `apps`, `applications`, `cowork`, `oauth`, `partner-connect`, `coupons`, `portals`, `app-blueprints`, `agents` families got no addressable `?scope=` value of their own and stay in the full specification only.

### FIX-0911-2: POST /v1/doc-templates refusals point to the file field, not to Drive

**Before**

The `MISSING_FILE_OR_FILE_ID` and `UNSUPPORTED_MEDIA_TYPE` refusals offered a second route: upload the file to Drive and reference it by `fileId`. A template created that way could not be rendered — `POST /v1/documents` answered `422 BITRIX_ERROR` with the `FILE_NOT_PROCESSABLE` marker.

**After**

Both messages name the working route: the `.docx` content as a base64 string in the `file` field. Refusal codes, statuses and trigger conditions are unchanged, only the message text differs. The `fileId` field is still accepted — the `GET /v1/guide` description now states that a template built from it cannot be rendered.

### NEW-0911-3: the Marketplace trial unavailability reason gained an eighth value

The `unavailableReason` field inside the `activation.marketTrial` block of [GET /v1/cowork/state](/docs/cowork/state) accepts a new value, `not_required_for_data`, in addition to the seven previous ones.

It means the platform no longer treats platform access as a precondition for reading company data with this key, so there is nothing to offer. The Bitrix24 account itself can still refuse in rare cases — keep handling the refusal.

The value is returned only on cloud accounts whose platform access is opened by a paid plan of that kind, and only once the matching platform setting is enabled. The other values keep their meaning, the field is still always present, and the rule "a `false` value is final, read an unknown value as do-not-offer" is unchanged — thanks to it a client built before this change behaves correctly without an update.

Access is still required for bot calls, deploying and waking applications, creating an application and replacing its key, binding placements and issuing an agent key.

### FIX-0911-4: Batch calls preserve selected fields

**Before**

[POST /v1/batch](/docs/batch) with `list` and `search` actions could omit fields from `select` that a single request returned: task custom fields such as `ufCrmTask` and declared CRM aliases such as `statusId`, `amount` and `currency`. Selecting tasks by `UF_CRM_TASK` also dropped the field from single-request responses.

**After**

The same named `select` preserves the same fields in single and batch reads. Task custom fields are also preserved when selected by `UF_CRM_TASK`, including `null` values.

**Impact on integrators**

Use named `select` instead of the `select: ["*"]` workaround. Custom field spellings for other entities remain unchanged.

### FIX-0911-5: audio transcription tells an input error apart from a service failure

**Before**

[POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) answered a file whose contents are clearly not audio — text, a document, an image or an archive — with `502 ai_provider_unavailable`, as it would a recognition-service failure. Clients retried such a request, and every retry failed again. An unknown ID in the `model` field was sent to recognition and came back as `400 ai_provider_rejected`.

**After**

Content that is clearly not audio is rejected with `400 invalid_audio` before recognition, and nothing is charged. An unknown model ID is rejected with `404 ai_model_not_found` before recognition — the same code as on `POST /v1/embeddings`. The `HTTP 200` response for audio files and known models is unchanged.

**Impact on integrators**

There is no point in retrying a `400 invalid_audio` request without replacing the file: show the error to the user instead. A client that does not retry `4xx` responses keeps working unchanged. If your handler matches specific codes, add `invalid_audio` and `ai_model_not_found` to it.

### FIX-0911-6: a write response now hints which fields were not recognised

**Before**

A typo in a field name in the body of `POST /v1/{entity}` or `PATCH /v1/{entity}/{id}` passed silently: the response was successful, and the field — an amount with a misspelled name, say — appeared nowhere.

**After**

The single-record write response stays successful with the same status, and `meta.warnings` carries one `UNRECOGNIZED_WRITE_FIELD` entry per body key that is neither in the entity description nor in your account's field list; `field` names the key. It is a hint, not a refusal: the record is created or updated as before, `meta` appears only when there is something to say, and there are at most ten hints per response. When the account's field list is unavailable there is no hint — the Vibecode platform never warns by guesswork.

### NEW-0911-7: The flow_ref field in the device-code response

`POST /v1/connect/device/authorize` returns a new optional `flow_ref` field — the identifier of THIS sign-in attempt, named the same way by the client and by the Vibecode platform.

It exists for analytics only. One device signs in many times — reconnect, account switch, retry after a refusal — and without a shared name for the attempt, the events of one sign-in cannot be told apart from the next one's. The client stamps `flow_ref` into its own events and gets an exact match instead of guessing by timestamps.

The field is not a secret, carries no access rights, and `user_code` cannot be recovered from it. A client that ignores it works exactly as before.

### NEW-0911-8: balances export: client identifier, single-account lookup and a delta

Four fields were added to the `GET /v1/platform/revenue/balances` row. `clientType` and `clientId` carry the client identifier as Bitrix24 billing knows it — the same value the Vibecode platform receives in the payment webhook metadata; the pair arrives whole or stays entirely empty. `portalNetworkId` is the Bitrix24.Network identifier of the account: unlike the domain, it survives a move. `updatedAt` is when the billing account last changed.

Self-hosted accounts always carry the client identifier. For cloud accounts it is known only from their own payments, so an account that never paid comes back with the pair empty — such rows can be matched by `portalNetworkId` or by domain.

New query parameters: `changedSince` (only accounts changed at or after the given moment, UTC ISO-8601 with `Z`), `portalId`, `portalDomain`, and `clientId` together with `clientType` (`b24` or `box`). A daily sweep shrinks roughly fivefold, and a single-account card no longer pulls the whole population.

The cursor is now bound to the filter set: continuing a walk with a different filter is refused with `400 INVALID_CURSOR` — start the walk again. A cursor issued before this change keeps working for an unfiltered walk. Existing row fields and the walk order are unchanged.

### BC-0911-9: the tax amount on deals and quotes is now read-only

> Old format supported until: not provided

**Before**

The `taxValue` field on [deals](/docs/entities/deals/fields) and [quotes](/docs/entities/quotes/fields) was declared writable and accepted with a `200`/`201` response, but Bitrix24 did not store it: the value stayed `0` on create — including manual-amount mode — on update and on import. There was no way to tell that apart from a successful write.

**After**

The field is declared server-assigned: Bitrix24 computes it from the product rows. A write is refused before Bitrix24 is called, on every door: create and update return `400 READONLY_FIELD`, import returns `400 IMPORT_ITEM_VALIDATION`, entity batch returns `400 BATCH_ITEM_VALIDATION`, and in the global batch the refusal arrives under the affected call in `data.errors` with the code `READONLY_FIELD` while the other calls of the envelope still run. The field description says where the tax is set: through the product rows. Measured 2026-09-11 on live Bitrix24 accounts of both platforms: create and update in both amount modes, import in both modes.

**What integrators should do**

Remove `taxValue` from the body of create, update and import requests for deals and quotes — including flows that read a record in full and send it back. The value was never stored; the tax is set through the product rows, and the field stays in read responses.

**Affected endpoints:** [POST /v1/deals](/docs/entities/deals/create), [PATCH /v1/deals/{id}](/docs/entities/deals/update), [POST /v1/quotes](/docs/entities/quotes/create), [PATCH /v1/quotes/{id}](/docs/entities/quotes/update), [POST /v1/{entity}/import](/docs/import), [POST /v1/{entity}/batch](/docs/batch), [POST /v1/batch](/docs/batch).

### FIX-0911-10: the messenger API description now matches what the endpoints answer

**Before**

The machine-readable description (`/v1/openapi.json`) of the chat, notification, knowledge-base and activity-feed endpoints diverged from their behaviour, and a client generated from it broke on the very first call.

`GET /v1/chats/find` was described with `entityTypeId` and `entityId` (integers) — the endpoint reads `entityType` and `entityId` (strings, e.g. `CRM` and `DEAL|123`) and always answered `400 MISSING_PARAMS` to the described form. `GET /v1/chats/search` was described with a `query` parameter — the endpoint reads `search`. `POST /v1/posts` was described with a `message` body property — the endpoint requires `text`.

Thirty-four operations of these families described no refusal at all — only `200`, `201` or `204` — while the endpoints answer `400`, `401`, `403`, `404` and `422`. A client generator built neither types nor handlers for them. Four operations (`POST /v1/chats/{chatId}/files`, `POST /v1/posts`, `POST /v1/posts/{id}/comments`, `DELETE /v1/posts/{id}/comments/{commentId}`) described success as `200` while the endpoints answer `201` and `204`.

**After**

As far as parameters and success codes go, endpoint behaviour is unchanged — the description is corrected (the behaviour changes are named separately below). `GET /v1/chats/find` takes `entityType` and `entityId`, required strings; `GET /v1/chats/search` takes `search`; the `POST /v1/posts` body is `text` (required), `title`, `recipients`, `files`. The success of the four operations is described with the status the endpoint actually answers, `201` and `204`. The successful response remains `200`, `201` or `204` exactly where it was: the description changes, the response does not.

Every operation of the chat, notification, knowledge-base and activity-feed families, plus `GET /v1/bots`, `POST /v1/bots` and `GET /v1/bots/revision`, now describes the refusals it actually answers, drawn from: `401` (`MISSING_API_KEY`, `INVALID_API_KEY`, `TOKEN_MISSING`), `403` (`SCOPE_DENIED`, `WRITE_BLOCKED_READONLY_KEY` on writes, `BITRIX_ACCESS_DENIED`), `404` (`ENTITY_NOT_FOUND` plus endpoint-specific codes), `422` (`BITRIX_ERROR`), and — where the endpoint validates its own input — `400` with its codes (`MISSING_PARAMS`, `MESSAGE_REQUIRED`, `INVALID_PARAMS`, `INVALID_POST_ID` and others).

Behaviour was straightened along the way as well — both changes are purely in the client's favour, no previously successful call is refused: `POST /v1/chats/messages/bulk` — a pure bulk read of message history — answered a read-only key with `403 WRITE_BLOCKED_READONLY_KEY`, because the generic `batch` container counted as a write; such a key now reads in bulk exactly as it reads one dialog at a time. Some chat and activity-feed write endpoints called with no request body at all (and no `Content-Type` header) answered `500`; such a call now takes the same path as an empty `{}` body: where a field is required the endpoint refuses on its own (`DELETE /v1/chats/{chatId}/users` — `400 MISSING_PARAMS` about `userId`, `POST /v1/posts` — about `text`), while chat creation, where every field is optional, forwards the request to Bitrix24 as is. Message editing (`PATCH /v1/chats/{dialogId}/messages/{messageId}`) is not part of this fix and is straightened in a separate change.

The bot family is described in full: every operation on a specific bot (`/v1/bots/{botId}…`, except deletion, re-authorization, re-subscription and ownership transfer, which carry their own sets) declares `400 INVALID_BOT_ID`, `404 BOT_NOT_FOUND`, `410 BOT_DISABLED` and `422` — the refusals of the shared bot lookup step and of the Bitrix24 call — as well as `401`.

The interactive reference (`/docs` → API reference) has been regenerated from the corrected description in both segments: the `GET /v1/chats/find`, `GET /v1/chats/search` cards and the `POST /v1/posts` example show the working parameters.

What the description of these operations STILL does not name — deliberately, because the decision belongs to the whole API rather than to the messenger: the balance refusal (`402 ACCOUNT_FROZEN`) and the `429 RATE_LIMITED`, `502 BITRIX_UNAVAILABLE`, `503` answers when Bitrix24 is unavailable or overloaded — they are possible on any operation that reaches Bitrix24 and will be declared by one rule across the whole description in a separate change. A client's error handler should be ready for them already.
