# API changes: August 10, 2026

[← Changelog](/docs/changelog) · [August 2026](/docs/changelog/2026-08)

### NEW-0810-1: issuing a share link is recorded in the account access journal

The [POST /v1/infra/servers/:id/access-tokens](/docs/infra/access-tokens) response is unchanged — what changes is what happens on the account side.

**Before**

Issuing a `mode=share-url` link left no trace in the account oversight layer: an administrator could not see who opened an application with a link, or when.

**After**

After a link is issued, the platform records the exposure change in the account journal, and — when the link does not require a Bitrix24 sign-in (`identityBound=false`) and the application was not already open to outsiders — sends the account administrators a chat-bot message linking to the list of open applications.

**Impact on integrators**

The request format, the response and the error codes are unchanged — no client change is needed. Note that every open link you issue is now visible to the account administrators.

### BC-0810-2: a filter error in POST /v1/{entity}/batch is now a per-sub-call error, not a whole-request one

> Old format supported until: 04.02.2027

**Before**

One bad filter key in any sub-call of [POST /v1/{entity}/batch](/docs/batch) cancelled the
whole request: `400`, the code in `error.code`, and the results of every other sub-call
discarded. The global [POST /v1/batch](/docs/batch) behaved differently — it placed the error
next to the sub-call that caused it and ran the rest.

**After**

Both surfaces behave the same way. The response is `200`, the refusal code sits in
`data[i].error.code` for the sub-call that caused it, and the remaining sub-calls run and
return their data. The set of refusals and their codes is unchanged — only the blast radius
is: `UNKNOWN_FILTER_FIELD`, `INVALID_FILTER_OPERATOR`, `INVALID_FILTER_FIELD`,
`UNSUPPORTED_FILTER`.

The message used to be prefixed with `Call at index N:` — the sub-call's position is now
visible from its place in the `data` array, so the prefix is gone.

**Integrator migration**

If your code reads a filter refusal as `HTTP 400` with `error.code`, add handling for `200`
with `data[i].error.code` — otherwise a sub-call whose filter was refused will read as
successful. Check each `data` element for an `error`, exactly as you already do for the
global `POST /v1/batch`.

### BC-0810-3: batch reads no longer bypass an operation the entity turns off

> Old format supported until: 04.02.2027

**Before**

An entity can turn a single operation off — usually because the generic handler is wrong for
it: the real list lives on its own route with a different envelope, or the Bitrix24 method
ignores the filter and returns the whole table. The single routes honoured that, and so did
batch writes, but batch reads did not. So [POST /v1/{entity}/batch](/docs/batch) and
[POST /v1/batch](/docs/batch) with `action` `list`, `get`, `search` or `fields` answered
`200` where the same operation answers `404` on its own route — serving exactly the result the entity
turned the operation off to avoid.

Separately, the legacy `GET /v1/{entity}/aggregate` never asked whether the entity has an
aggregate at all. For an entity whose every numeric field is an identifier,
`POST /v1/{entity}/aggregate` answers `404` while this address answered `200`.

**After**

Both batch surfaces answer a disabled operation with `400 ACTION_NOT_SUPPORTED` before any
Bitrix24 call: on the per-entity batch that is the whole request, on the global one it is a
per-sub-call error and the remaining sub-calls still run. The legacy
`GET /v1/{entity}/aggregate` is registered by the same predicate as its `POST` sibling, so an
entity without an aggregate now answers `404` on both.

**Integrator impact**

Only the entity-and-operation pairs whose answer was already wrong are affected. Check all
four reads — `list`, `get`, `search`, `fields`: six entities disable `search` while keeping a
working `list`, so a batch that used to go through whole now answers `ACTION_NOT_SUPPORTED`
on that sub-call. The full
set of actions is in `operations.batch` of the `GET /v1/guide` response and in the OpenAPI
description; `data.batch` of `GET /v1/{entity}/fields` lists WRITE actions only and cannot
answer the question about reads. An entity with every
operation off keeps its route, but every action answers `ACTION_NOT_SUPPORTED` — the refusal
names the action, which a `404` on the path cannot.

### BC-0810-4: a `filter` that is not an object is refused instead of being lost

> Old format supported until: 04.02.2027

> As in the sibling entry about an unknown field name, "the old format" here means a wrong
> answer, not a working one.

**Before**

`filter` is an object of conditions, but nothing stopped a client sending a string, a number,
a boolean or an array instead. A string and an array went to Bitrix24 as they were, a number
and a boolean became an empty filter — and in every case the request answered `200` with the
WHOLE collection:

```
POST /v1/tasks/search   { "filter": [{ "responsibleId": 1 }] }   ->  200, every task on the account
```

**After**

Such a request is refused before the Bitrix24 call — `400` with the code
`INVALID_FILTER_SHAPE`; the message says what arrived instead of an object and shows the
correct form.

This applies on every entity and every surface where `filter` arrives in a body: search,
aggregate and both batches.

**Integrator migration**

Send `filter` as an object. The query string is a separate story: conditions are written in
the bracket form there (`?filter[responsibleId]=1`), and a `filter` JSON-encoded as one string
is no longer dropped as of this release — it is parsed and applied, where it used to be lost
silently and return the whole collection.

### BC-0810-5: an unknown filter field name is refused on fifteen more entities

> Old format supported until: 04.02.2027

> Until now such a filter answered `200` with the whole collection — so "the old format" here
> means a wrong answer, not a working one.

**Before**

A filter on a name the entity does not have was sent to Bitrix24. Bitrix24 does not refuse
such a key — it drops it silently and answers `200` with the WHOLE collection. A typo in a
field name therefore looked like a successful request with an implausibly large result:

```
GET /v1/tasks?filter[responsable]=1     →  200, every task on the account
```

That was the behaviour of [tasks](/docs/entities/tasks/list),
[users](/docs/entities/users/list), [workgroups](/docs/entities/workgroups/list),
[requisites](/docs/entities/requisites/list), [bank details](/docs/entities/bank-details/list),
[requisite presets](/docs/entities/requisite-presets/list),
[addresses](/docs/entities/addresses/list), [sites](/docs/entities/sites/list),
[pages](/docs/entities/pages/list), [timeline comments](/docs/entities/timelines/list),
[workflow templates](/docs/entities/bizproc-templates/list), Open Channels configs and
universal-list elements.

**After**

Such a request is refused before the Bitrix24 call — `400` with the code
`UNKNOWN_FILTER_FIELD` and the available names listed in the message — that list IS the
precise answer to "what can I filter by".

Only the NAME is checked: operators, ranges, `$in`/`$nin` and AND logic behave as before.
Besides declared fields it accepts custom fields (`UF_*`, `ufCrm*`), and the `id` key on the
entities that declare one.

Separately, for [workflow activities](/docs/entities/bizproc-activities/list) and
[workflow robots](/docs/entities/bizproc-robots/list) the Bitrix24 method accepts no filter in
any form, so any filter key there is refused with the code `UNSUPPORTED_FILTER`.

Full description — [Filtering and search](/docs/filtering#an-unknown-filter-field-name).

**Integrator migration**

Check the field names in your filters against the list in the error message. A request that used
to "work" but returned more records than it should will now answer `400` naming exactly which
field was not found — which is the mistake it always contained. Other filters are unaffected.

### FIX-0810-6: a host out of free disk space is a distinct retryable deploy failure, not a broken app

**Before**

When the shared host of a galaxy app ran out of free disk space, the build failed and [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) answered `502 GALAXY_APP_BUILD_FAILED` — the same code as an error in the app's own source. The app was marked broken even though it never stopped working: the failure happened during the build, before the container was replaced, so the previous version went on answering requests. Telling a full disk apart from a genuine build error was only possible from the log tail in `buildLog`, and re-sending the same deploy produced the same result.

**After**

The same case returns `502 GALAXY_LOW_DISK` with `retryable: true` and a structured `error.hint`: the cause, what to do, and a warning not to delete the slot. The app is not marked broken — the slot, its container and its data volume are intact, and the version already running keeps serving traffic. Re-send the deploy once space has been freed on the host: disk is not reclaimed on its own, so an immediate retry hits the same refusal. The new code is listed in the machine-readable deploy contract returned by [GET /v1/me](/docs/quickstart) and `GET /v1/openapi.json`.

**Impact on integrators**

Nothing to change: successful deploys are unaffected. Keep your branch on `GALAXY_APP_BUILD_FAILED` — it still arrives for genuine build errors, while a full disk now has its own code that makes clear the problem is not in your source. An automatic retry happens only where the platform rebuilds an app that already exists: there attempts continue without client involvement until the wait budget runs out. When an app is created together with its source in one step, the refusal arrives right away and is not retried — the decision to retry is yours.

### FIX-0810-7: A catalog launch now warns that the app is not authorized

**Before**

An app opened from the Bitrix24 app catalog by a user who had not granted it access yet started as usual, but the gateway did not set the `X-Vibe-Authorization` header. API calls answered `401`, and the app displayed the text our own documentation prescribed for a `401` — "re-open the app from the Bitrix24 menu". That advice led nowhere: the user had just done exactly that, and a catalog launch does not grant access.

**After**

The platform now intercepts such a launch and shows a screen with an "Authorize the app" button; the second route is to open the app once through a placement in the Bitrix24 menu. The same screen carries an unobtrusive "open without authorizing" link to the very same launch address, so an app that does not need the session opens exactly as before. The screen appears only where the button has somewhere to lead: a cloud account, an app key, and a registered OAuth application. Every other launch behaves as before.

**Impact on integrators**

No code changes are required, and no launch becomes unavailable. Only your own `401` message is worth revising: the status has two causes — an expired session and access that was never granted — so "re-open from the menu" as the single wording misleads the user. The recommendation is updated in [App runtime](/docs/infra/app-runtime).

### NEW-0810-8: CRM document list now present in the machine-readable OpenAPI schema

The [GET /v1/crm-documents](/docs/entities/documents/crm-list) endpoint is now described in the OpenAPI schema served at `/v1/openapi.json`. The call itself already worked, but clients and AI agents that build integrations from the machine-readable description treated it as non-existent. The description states the required `entityTypeId` parameter, the optional `entityId`, `select`, `order` and `start`, the required `crm` access, the response shape with the document array and the `meta` block holding `total`, `start` and `next`, plus the refusal codes `MISSING_PARAMS`, `INVALID_ENTITY_ID`, `INVALID_START`, `TOKEN_MISSING` and `SCOPE_DENIED`. The endpoint takes no page-size parameter and the description declares none: for the next page pass the `meta.next` value as `start`. The behaviour of the endpoint itself is unchanged.

### FIX-0810-9: a direct-link visit now carries the visitor's name and access token

**Before**

An app opened by its direct address (rather than from its tile inside Bitrix24) received the visitor's name from their Vibecode account instead of their employee card: someone who belongs to several Bitrix24 accounts saw the name they hold in a different one. The `X-Vibe-Authorization` header did not arrive on this path at all, so the app could not call Bitrix24 on the visitor's behalf.

**After**

`X-Vibe-User-Name` and `X-Vibe-User-Name-Encoded` carry the name from the employee card of the Bitrix24 account that owns the app — the same as on the tile path. The `X-Vibe-Authorization` access token is resolved by the employee id, so it arrives here too.

**Impact on integrators**

The header format is unchanged, no client work is needed. When the employee card cannot be read, the name stays as before, taken from the Vibecode account: the header is never delivered empty.

### FIX-0810-10: invoice user fields

**Before**

Invoice user fields could not be read or created through either path.
[GET /v1/userfields/invoices](/docs/userfields/smart-processes) answered `UNKNOWN_ENTITY`, and
[GET /v1/items/31/userfields](/docs/userfields/smart-processes) answered with a Bitrix24 access
error, because the invoice was addressed the same way as an ordinary smart process.

**After**

Both paths work and return the same result: six operations (list, type catalog, read, create,
update, delete) over invoice user fields. The `/v1/userfields/invoices` path is there for callers
who prefer the entity name over the numeric type id.

**Impact on integrators**

No action required. This covers invoices in their current form; legacy invoices remain
unavailable through the API.

### BC-0810-11: The product-row field reference now describes what the responses actually contain

> Old format supported until: not provided

**Before**

[GET /v1/deals/{id}/products/fields](/docs/entities/deals/products-fields) returned the Bitrix24 field set verbatim, and it disagreed with the product-row responses in three places at once. The discount amount was called `discountSum` in the reference but `discount` in the data and on write. The external code and the account-currency price arrived in every row yet were missing from the reference. Owner, owner type and warehouse were the other way round: present in the reference, stripped from the responses.

Worse, a client that generated its writer from this reference sent `discountSum` — the wrapper did not recognise the name, answered `201 Created` and discarded the discount silently. Any other unknown field behaved the same way: success reported, data not written.

**After**

The reference is derived from the same tables that build the responses, so the two can no longer drift apart. The discount is called `discount`, matching the data, the write contract and the documentation. The previous name `discountSum` did not go away: it remains a deprecated alias, still returned by the reference and still accepted on write, so code written against the old field list keeps working — except that the discount now actually lands instead of being dropped silently. Product rows themselves carry only `discount`. `priceAccount` and `xmlId` were added — Bitrix24 returns them in rows but does not describe them in its own field set. `ownerId`, `ownerType` and `storeId` now arrive in the [list](/docs/entities/deals/products-get) and [single row](/docs/entities/deals/products-get-single) responses; `storeId` is `null` when inventory management is off.

`isReadOnly` and `isRequired` describe this API's contract rather than the Bitrix24 one: `ownerId`, `ownerType`, `customized` and `measureName` are marked read-only because the wrapper cannot write them, and `ownerId` and `ownerType` are no longer required — they are taken from the request path. `id` gained a description: it is read-only as an attribute, and inside [PUT /products](/docs/entities/deals/products-set) items it is accepted. **Correction of 2026-08-18:** a live check showed that echoing `id` back does not preserve row identity — a row whose fields are unchanged keeps its `id` even with no `id` in the body, and a modified row comes back with a new `id`. To edit a row and keep its identifier, use `PATCH /v1/deals/:id/products/:rowId`.

A write carrying an unknown field name no longer reports success: [POST](/docs/entities/deals/products-add), [PUT](/docs/entities/deals/products-set) and [PATCH](/docs/entities/deals/products-update) return `400 INVALID_PARAMS` and list the writable fields. Read-only fields are still accepted and ignored, so an object read back via GET can be sent as is.

A body `id` is accepted only inside [PUT /products](/docs/entities/deals/products-set) items. On [create](/docs/entities/deals/products-add) and [update](/docs/entities/deals/products-update) it is dropped: the row is identified by the request path there, and a body carrying the id of an existing row is exactly what you get by sending back an object read via GET.

On write, `taxIncluded` accepts a boolean again: `true` and `false` reach Bitrix24 as `Y` and `N`. The boolean used to be forwarded as is, leaving the row's tax inclusion effectively unset — so a row read via GET and sent straight back lost the flag. If you worked around this by sending `"Y"` and `"N"` as strings, nothing changes: that form is still accepted.

When Bitrix24 returns incomplete metadata the reference still comes back complete, but the response now carries `meta.warnings[].code = "fields_partial"` — previously such an answer was indistinguishable from a healthy one. The warning text distinguishes two cases: no metadata arrived at all, or only some fields were left undescribed (it then names them).

**What integrators should do**

**What breaks is a write carrying a foreign key in the body.** Check that creating or updating a product row sends nothing beyond the writable fields — your own bookkeeping markers, leftovers from an internal model, Bitrix24 upper-case names (`PRICE_ACCOUNT`, `XML_ID`). Such a request used to answer `201 Created` and discard the value silently; it now answers `400 INVALID_PARAMS` and lists what is accepted. There is deliberately no support window for the old behaviour: it was the defect this change exists to fix, and keeping it alongside means keeping silent data loss. The full list of writable fields comes back in the refusal and in the [field reference](/docs/entities/deals/products-fields).

Nothing else needs changing. `discountSum` is still returned and still accepted — move to `discount` at your own pace, that is the name product rows carry, while the alias lives only in this reference. If you branch on `isReadOnly` or `isRequired`, re-check the new values for `ownerId`, `ownerType`, `customized` and `measureName`. If you replace rows wholesale, note that the identifiers of modified rows change: re-read the set via GET after the write.

### FIX-0810-12: server member lookup no longer comes back empty because of a recent key without Bitrix24 access

**Before**

When a server's managing key granted no Bitrix24 access, `GET /v1/infra/servers/{id}/b24-users`
and the member lookup in the interface fell back to the server owner's personal keys, but checked
only one — the most recently created eligible key. If that key had neither a webhook nor an
installed application token, the response came back empty (`data: []` with a hint) even though the
owner had another active key with the required rights. Keys the platform issues itself for tasks
that never call Bitrix24 are always the most recent ones, so the lookup could stay broken
indefinitely.

**After**

All eligible personal keys of the owner are checked, newest to oldest, and the first one that
really has Bitrix24 access is used. Keys without access are skipped, and Bitrix24 still receives a
single request. Key requirements are unchanged: as before, only an active, non-expired personal key
of the server owner in the same account qualifies, and it must not be bound to an application.

The `hint` text is corrected as well: it used to name only an unauthorized app and a revoked key, so
an active key looked revoked. A third reason is now named — no key grants Bitrix24 access — together
with the action to take, issuing a personal key with the required scope. The response shape is
unchanged.

### FIX-0810-13: placement bind names the install-rights refusal instead of a generic gateway error

**Before**

Bitrix24 refused the embedding install and the account's plan state could not be confirmed at that moment — [POST /v1/placements/bind](/docs/apps/placements/bind) answered `502 BITRIX_UNAVAILABLE` and put the Bitrix24 code into `details`. There was no named code in the answer, and no hint about what to do next. The refusal reached accounts whose plan already allowed the install.

**After**

When the check cannot be performed and the account is already on record as entitled in Vibecode, the same refusal arrives as `403 B24_EMBEDDING_INSTALL_DENIED` with `details.remedy` set to `install-rights`. No upgrade link is attached: the entitlement is there, what is missing is the right to install local applications. With no live entitlement from either source the answer stays `502 BITRIX_UNAVAILABLE`.

**Impact on integrators**

Nothing to change. Your branch on `502 BITRIX_UNAVAILABLE` keeps working for the other refusals, while this case leaves it for `403`. Retrying it is pointless — the developer key has to belong to a user allowed to install local applications and holding access to the application.

### BC-0810-14: commands, deploys and file uploads addressed by a galaxy host id are refused

> Old format supported until: 07.08.2026

**Before**

A galaxy is a single machine that hosts the applications of several keys of one Bitrix24 account, each in its own container. The server list returns both the applications (`kind=GALAXY_APP`) and the machine carrying them (`kind=GALAXY`), and calling [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec), [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) or [POST /v1/infra/servers/:id/upload](/docs/infra/deploy/upload) with the id of that machine ran on the machine itself, that is outside the caller's own container.

**After**

The same call against a `GALAXY` machine is refused: commands return `403 GALAXY_HOST_EXEC_FORBIDDEN`, deploys and file uploads return `400 GALAXY_HOST_NOT_A_DEPLOY_TARGET`. The refusal names the replacement: address the application by its own id (`kind=GALAXY_APP`). Nothing changes for `STANDALONE` machines or for galaxy applications.

**What integrators should do**

Take your application's row from the server list (`kind=GALAXY_APP`) and use its id. If the flow relied on reaching the machine to learn how much disk space is left, that figure is now available as data rather than as the output of a command.

There is no support window for the previous behaviour: the refusal applies from the moment this ships. The reason is that the previous behaviour opened access to a machine carrying other keys' containers, and access like that cannot be kept alive for six months for the sake of compatibility.

### NEW-0810-15: galaxy host disk usage is exposed in the server list

A galaxy host row (`kind=GALAXY`) in [GET /v1/infra/servers](/docs/infra/servers/list) and in the single-server response now carries four fields: `diskTotalMb` and `diskFreeMb` — size and free space in mebibytes, `diskState` — the verdict (`ok`, `warning`, `critical`, or `unknown` when nothing has been measured yet), `diskProbedAt` — the measurement time in ISO-8601.

The platform keeps both lines well clear of the edge: `warning` means space is running out, `critical` means less is left than the platform considers safe; both light up before a deploy actually runs out of room. The measurement refreshes on its own while the machine is awake; a sleeping machine shows the last known value together with its timestamp.

For `STANDALONE` machines and for galaxy applications (`kind=GALAXY_APP`) all four fields are `null`: the former are never probed, the latter have no disk of their own.

### NEW-0810-16: agent model bitrix/bitrixgpt-5.6-agent

[GET /v1/models](/docs/ai/models/list) now lists a new agent model, `bitrix/bitrixgpt-5.6-agent`, with a 1,048,576-token context. It supports streaming, tool calls (`tools`) and schema-constrained output — `response_format` with `type: "json_schema"`. The model public id is part of the `ai.structuredOutputs.models` list returned by [GET /v1/me](/docs/keys-auth/me).

The model is additive and replaces nothing: existing calls are unaffected. It is enrolled in the quota programme, so it is also reachable with a Bitrix24 partner token.

**Affected endpoints:** [GET /v1/models](/docs/ai/models/list), [POST /v1/chat/completions](/docs/ai/chat/completions), [GET /v1/me](/docs/keys-auth/me)

### NEW-0810-17: bitrix/bitrixgpt-5.5-agent is deprecated

Responses from [POST /v1/chat/completions](/docs/ai/chat/completions) for `bitrix/bitrixgpt-5.5-agent` now carry `Deprecation: true`, `X-Model-Replacement: bitrix/bitrixgpt-5.6-agent` and a `Link` header pointing at the successor (`rel="successor-version"`).

The model keeps working without restrictions and stays in the [GET /v1/models](/docs/ai/models/list) listing. No shutdown date is set — the `Sunset` header is not sent, and no integration changes are required. The successor for new integrations is `bitrix/bitrixgpt-5.6-agent`.

### FIX-0810-18: a transient database transaction failure no longer returns 500 with an internal engine code

**Before**

When a database transaction closed or expired before the operation finished, the request answered `500` and put the engine's internal code in the body — `{"error":{"code":"P2028"}}`. That code appears on no documentation page, the response carried no `Retry-After` header, and nothing in it said the request was worth repeating. It showed up most often on `DELETE /v1/apps/{id}`: the application stayed in place and a retry looked pointless.

**After**

The same class of failure returns `503` with code `DB_TRANSIENT`, an `error.retryAfter` field, and a `Retry-After` header — the same retry posture as `POOL_EXHAUSTED`. The change applies neither partially nor fully on such a failure, so a straight retry after a few seconds is safe. On the AI routes (`/v1/ai/*`, `/v1/chat/*`, `/v1/audio/*`, `/v1/models`) the code arrives lowercased — `db_transient` — in the OpenAI-compatible envelope. The engine's internal code no longer appears in the response body.

**Impact on integrators**

Nothing to change. If your handler treated `500` as a terminal refusal, this case now arrives as `503` with a stated delay and falls into your retry branch instead. No special handling of the `DB_TRANSIENT` code is required: honouring `Retry-After` on any `503` is enough.

### FIX-0810-19: preserveEnv no longer loses .env when a deploy fails after the directory is cleaned

**Before**

With `cleanDeploy: true` together with `preserveEnv: true` the existing `.env` was read before the
clean but written back only at the `env` step — after the runtime and the dependencies were
installed. If the deploy aborted earlier, [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy)
answered `DEPLOY_FAILED` and the directory was left with no `.env` at all. The application
settings had to be uploaded again by hand.

**After**

The saved copy is put back on disk on any abort after the clean — on dependency install, on the
runtime, on the archive download, on the clean itself, and when the connection to the server
drops. The restore is best-effort and adds no separate step to the response. The precedence rule
is unchanged: an `env` passed in the same request still wins, and an explicit empty `env: {}`
means "clear it" and does not bring the saved copy back. A successful deploy behaves exactly as
before, including the time-zone injection for wake schedules. The flag still applies to a
dedicated virtual machine only (`kind: "STANDALONE"`).

**Impact on integrators**

Nothing to change on your side. The `DEPLOY_FAILED` response shape and the set of steps in
`data.steps` are unchanged. If you worked around this defect by re-uploading `.env` by hand after
every failed deploy, that workaround is no longer needed.
