# API changes: September 10, 2026

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

### BC-0910-1: a network failure on the way to the provider arrives under its own code ai_provider_network

> Old format supported until: not provided

**Before**

When the platform could not connect to the model cluster, or the connection dropped before its response, [POST /v1/chat/completions](/docs/ai/chat/completions), [POST /v1/embeddings](/docs/ai/embeddings) and [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) answered `502` with the general code `ai_provider_unavailable` — the same code as an error the cluster itself answered with. The two cases could not be told apart by the response code.

**After**

The same `502` response carries the code `ai_provider_network`. The `server_error` type and retryability are unchanged. This response carries no `providerStatusCode` field: the cluster never answered. The code `ai_provider_unavailable` stays reserved for the cases where the cluster did answer with an error. The original status arrives in `providerStatusCode` whenever the cluster answered with an HTTP status — including a refusal to open the stream after the stream itself has already started. The field is absent where there was no status: the error arrived as a frame from the body of an already open stream (such a frame carries the field only under the code `rate_limit_exceeded`) or the failure happened while processing the cluster's response. In streaming mode the same code arrives in the error frame before `data: [DONE]`, together with the `retryable` and `retryAfter` fields. Speech recognition reports a network failure under the same code `ai_provider_network`.

**What integrators should do**

A client that decides on a retry by the `502` status, the `retryable` field or the `server_error` type changes nothing. A client that recognises a network failure by the code `ai_provider_unavailable` adds the code `ai_provider_network` to that check. The previous code is not returned in this scenario from the release on: there is no transition period, because one response cannot carry two codes.

### FIX-0910-2: AI provider errors now carry the pause from the provider's own header, in one shape for every stream

**Before**

When the model cluster answered `429`, the pause handed to the client was taken from the text of the cluster's response, and with no number in the text a fixed ten seconds was used. The cluster's own `Retry-After` header was lost. In the streaming mode of the Cowork/Code fallback model a provider error arrived as the frame `{ "error": { "message": "fallback stream error", "type": "server_error" } }` without the `code`, `retryable`, `retryAfter` and `providerStatusCode` fields, so a cluster refusal on a rate limit was indistinguishable from its internal error. A `{ "error": … }` frame that the cluster sends inside an already open stream reached the client as an ordinary response fragment, and the call was journaled as successful.

**After**

The pause is taken from the cluster's `Retry-After` header. The synchronous `rate_limit_exceeded` response carries it in its own `Retry-After` header, the streaming one in the `retryAfter` field of the error frame before `data: [DONE]`. A number from the response text and the default value are used only when the header is absent. The error frame of the Cowork/Code fallback stream is built the same way as the frame of the main stream: the `code`, `type`, `retryable`, `retryAfter` and `providerStatusCode` fields are present under the same rules. An error frame that arrives inside an open stream becomes the platform's standard error frame: the response remains a stream, after the fragments already delivered a frame with `code`, `type`, `retryable` and `retryAfter` arrives, then `data: [DONE]`, and the call is journaled as an error. A frame raised from the body of an open stream carries `providerStatusCode` only under the code `rate_limit_exceeded`: from the body the platform accepts the status `429` alone, and the other body errors arrive under `ai_provider_unavailable` without that field. A refusal by the cluster with an HTTP status while opening the stream arrives as an `ai_provider_unavailable` frame with `providerStatusCode`, exactly like the synchronous response.

**Impact on integrators**

Nothing needs to change. A client that already reads the `Retry-After` header and the `retryAfter` field receives a more accurate pause. A client of the Cowork/Code fallback stream can now tell refusals apart by `code` and `providerStatusCode`, exactly as in the main stream.

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

### FIX-0910-3: MCP tools mark API refusals as errors

**Before**

Outside entity tools, an API response with `success: false` could be returned as an MCP result without an error flag.

**After**

These responses carry `isError: true`, including local refusals and network errors with `success: false`. The diagnostic JSON text is preserved. Successful responses remain unchanged and omit `isError`.

**Impact on integrators**

MCP clients that honor `isError` now recognize these responses as tool errors rather than successful calls. Integrations that ignore this flag and check `success` in the JSON text continue to work as before: the JSON format and contents are unchanged.

### FIX-0910-4: the QUEUE_TIMEOUT refusal message names the actual wait

**Before**

In a [429 QUEUE_TIMEOUT](/docs/errors/limits) refusal the `userMessage` field named 30 seconds of waiting in the Bitrix24 queue. The number was baked into the message text and did not match the wait the platform actually applied.

**After**

`userMessage` names the wait that applied to this call. The refusal still arrives with the `QUEUE_TIMEOUT` code, the `retryAfter` field and the `Retry-After` header — take the retry delay from those, not from the text.

**Impact on integrators**

No code changes are needed: the response shape is unchanged. A client that sized its own timeout by this message now reads the real queue wait limit.

### NEW-0910-5: external membership is visible in V1: an external value for access.via and an externalServers block in /v1/me

The `access.via` field of `GET /v1/infra/servers/{id}` has a new value, `external` (alongside
`owner`, `collaborator` and `galaxy-reference`). It means the caller was invited to this server
from ANOTHER Bitrix24 account: the code surface (deploy, exec, file upload, logs, reading and
downloading sources, wake) is open and listed in `access.allowedEndpoints`, while the owner
account has no dashboard for them at all. Such a row carries its own `access._note`, describing
the boundary of the invitation rather than development-team membership — the former shared text
sent an agent looking for an interface it is not entitled to. The other values — `owner`,
`collaborator` and `galaxy-reference` — are unchanged.

`GET /v1/me` now carries a new `externalServers` section inside `data.infra`, next to
`collaboratorServers`. It lists servers on other Bitrix24 accounts the key owner was invited to
as an external collaborator: `total`, `count` and `items` with `id`, `name`, `role`,
`companyPortalDomain` (the account the server belongs to) and `expiresAt` (`null` means no
expiry); `items` is capped at 100 rows, and `total` greater than `count` means the list was
truncated. There are two sections rather than one because the credential differs: a regular key
does not reach a server on another account — every external membership has its own key, which
the person manages in their own account under "External access". Neither section counts towards
`infra.limits`; those machines are not theirs.

The section is OPTIONAL: the key appears only when cross-account collaboration is enabled for the
Bitrix24 account of the company that owns the server, and at least one live external membership
exists for it. An absent section means "none visible", not "none exist": a row also drops out of
the response when the owning company has disabled collaboration on its side, even while the
membership itself is still technically live. A direct call to `GET /v1/infra/servers/{id}`
confirms access RIGHT NOW: a 200 means membership is live AND the owning company has
cross-account collaboration enabled. A 403 `EXTERNAL_COLLABORATOR_NOT_A_MEMBER` means "no
access right now" and does not distinguish between the two possible reasons — no membership, or
the owner turned collaboration off: a client cannot tell them apart today.

Both changes are additive: no existing response field is touched, and a client unaware of them
keeps working exactly as before.

### FIX-0910-6: X-Vibe-User-Name on a catalog open now carries the employee card name

**Before**

Opening an app from the Vibecode catalog put the platform account name in the header: for some employees that is a login, an e-mail address, or `Unknown`. Opening the same app from its Bitrix24 tile delivered the employee card name.

**After**

Both paths deliver the employee card name saved when the app was authorized. With no saved name the chain is unchanged: account name, then e-mail address, then `Unknown`.

**Impact on integrators**

No client change is required. For some apps the header value changes towards the employee name. The name stays a display value: use `X-Vibe-User-Id` as the account identifier and as the basis for access checks.

### FIX-0910-7: responses of a model hosted by a third-party provider name the public model id

**Before**

For a catalog model hosted by a third-party OpenAI-compatible provider under a public id, the `model` field of the `POST /v1/chat/completions` response and of every stream event echoed the provider's internal model name instead of the requested public id. Provider error text named the internal name as well.

**After**

The `model` field in the response and in every stream event equals the public model id the client sent in the request, as for every other catalog model. Provider error text names the same public id. A successful response stays successful; the envelope shape is unchanged.

**Impact on integrators**

A client that compares `model` in the response with the requested id now gets a match for these models too. An integration that parsed the internal model name out of `model` or out of error text must switch to the public id: the internal name is no longer returned.

### BC-0910-8: GET /v1/me answers 401 for a deleted key instead of 200

> Old format supported until: not provided

**Before**

[GET /v1/me](/docs/keys-auth/me) returned `KEY_NOT_FOUND` as the body `{"success": false, "error": {"code": "KEY_NOT_FOUND", "message": "Key not found"}}` under status `200 OK`. That was the answer to a call made with a key deleted while the request was being processed. A revoked key does not get this error — it gets `KEY_INACTIVE`.

**After**

The same refusal arrives under status `401` with the same body. The body shape is unchanged.

**What integrators should do**

A client whose HTTP library throws on 4xx by itself (`axios` with the default `validateStatus`, `ky`, `got`, `ofetch`, Guzzle with `http_errors`) must change its code: it no longer reaches the response body, and an exception fires instead of the branch on the `success` field. Handle the `401` and issue a new key on it.

A client deciding success by HTTP status (`res.ok`, `response.raise_for_status()`) used to take a deleted key for a working one and parse the refusal body as a `/v1/me` response. It now receives a `401` — check that the error branch leads to issuing a new key rather than repeating the same request.

A client that reads the `success` field from the body and does not throw on 4xx (`fetch` with no status check) works unchanged.

### BC-0910-9: a Bitrix24 credential refusal now arrives as 401, not 422

> Old format supported until: not provided

**Before**

When Bitrix24 rejected the credentials a key calls the account with (a revoked or expired
webhook), the call answered `422 BITRIX_ERROR` and the account's own code arrived in the
extra `error.b24Code` field. On that status the refusal looked like a request-data error, so
clients retried it — on a live account that added up to roughly 2,300 useless retries a day.

**After**

The same refusal arrives as `401 PORTAL_CREDENTIALS_REJECTED`, with a hint saying a retry
will not help until the key is reconnected. The code arrives on any route that reads account
data, and in a `POST /v1/batch` sub-error. Reference — [Authorization, keys and rights](/docs/errors/auth).

**What integrators should do**

If you branch on `422 BITRIX_ERROR` with `error.b24Code` equal to `authorization_error` or
`INVALID_CREDENTIALS`, move that branch to `401 PORTAL_CREDENTIALS_REJECTED` and reconnect the
key (`POST /api/keys/:id/reconnect`) instead of retrying. Other `error.b24Code` values in
`422 BITRIX_ERROR` are unchanged.

### FIX-0910-10: disk move and copy operations are now available in OpenAPI

**Before**

Five live disk operations were absent from `/v1/openapi.json` and from the generated API reference cards. Four of them — moving and copying a file and a folder — were described on documentation pages, while listing the contents of a storage root was described nowhere. An agent that learns the platform from the machine schema concluded that no such methods exist. The file download card carried a separate falsehood: its summary and description promised a link, while the method returns bytes.

**After**

OpenAPI and the API reference cards describe [POST /v1/storages/{id}/children](/docs/entities/storages), [POST /v1/files/{id}/moveto](/docs/entities/files/moveto), [POST /v1/files/{id}/copyto](/docs/entities/files/copyto), [POST /v1/folders/{id}/moveto](/docs/entities/folders/moveto) and [POST /v1/folders/{id}/copyto](/docs/entities/folders/copyto) — with the `disk` scope, the request body and the full set of refusal codes. The [GET /v1/files/{fileId}/download](/docs/entities/files/download) operation declares a binary response and is described as a byte stream rather than a link. Method behavior is unchanged, a successful response remains HTTP 200, and the existing error statuses and schemas are preserved.

### BC-0910-11: a custom AI provider no longer follows redirects

> Old format supported until: not provided

**Before**

If the base URL of a `custom-openai-compat` provider answered with an HTTP redirect (301, 302, 303, 307 or 308), the platform followed it and sent the request to the new address. This applied to key verification, model catalog fetching and model calls.

**After**

The platform does not follow the redirect and returns a refusal.

- [POST /v1/ai/credentials](/docs/ai/credentials/create) and [PATCH /v1/ai/credentials/:id](/docs/ai/credentials/update) answer `400 base_url_redirect`.
- [POST /v1/ai/credentials/:id/test](/docs/ai/credentials/test) still answers HTTP 200 with `valid: false`, and `data` now carries a `code` field with the value `base_url_redirect`.
- [POST /v1/ai/credentials/:id/fetch-models](/docs/ai/credentials/fetch-models) answers `400 base_url_redirect` instead of a response with `PROVIDER_LIST_MODELS_UNAVAILABLE`.
- [POST /v1/chat/completions](/docs/ai/chat/completions) with a model of such a provider answers `400 ai_provider_redirect_blocked`, and in streaming mode an error frame with the same code arrives. Retrying the request will not help.

**What integrators should do**

Set `baseUrl` to the final API address the redirect points to. A common case is `https://` instead of `http://`. Update an already saved key with [PATCH /v1/ai/credentials/:id](/docs/ai/credentials/update).

### BC-0910-12: task, telephony and universal-list endpoints no longer ignore filter in silence

> Old format supported until: not provided

**Before**

Several list endpoints answered `200` with a wider set than requested when the request carried a
`filter` parameter, and the response gave no way to tell an applied filter from a discarded one.

The first cause: the two spellings of `filter` are written by the query-string parser into the same
place, so the second one replaces the first. `?filter[AUTHOR_ID]=1&filter=` left an empty value,
which read as "no filter", and Bitrix24 was called without one:
[GET /v1/tasks/{taskId}/comments](/docs/entities/task-comments/list),
[GET /v1/calls/statistics](/docs/telephony/analytics/statistics),
[GET /v1/lists/{iblockId}/sections](/docs/lists/sections/list) and
[GET /v1/lists/{iblockId}/elements](/docs/lists/elements/list). For the same reason a condition
spelled with brackets deeper than two levels (`?filter[a][b][c]=1`) never reached the `filter`
parameter at all.

The second cause: endpoints that have no filter neither read nor rejected the parameter —
[GET /v1/tasks/{taskId}/history](/docs/task-history),
[GET /v1/tasks/stages/{entityId}](/docs/task-stages),
[GET /v1/tasks/{taskId}/checklist](/docs/entities/tasks/checklist/list),
[GET /v1/lists](/docs/lists/lists/list) and
[GET /v1/voximplant-lines](/docs/telephony/lines/voximplant).

**After**

A `filter` envelope from which at least one condition you spelled did not reach the parameter is
rejected with `400` and the code `INVALID_FILTER`, before the Bitrix24 call. A condition is lost
when both forms are mixed in one request (in either order), when two spellings address the same
condition (`?filter[a]=1&filter[a][b]=2`), and when bracket nesting goes deeper than two levels —
the query-string parser does not assemble that. The error text names which of these happened. Pass
the whole filter in a single form, spell every condition once, and stay within two levels.

Endpoints that have no filter answer `400` with the code `UNSUPPORTED_FILTER` and name what to use
instead: `field` for task history, `sort` and `offset` for the list of universal lists.

An empty `?filter=` still means "no filter", and the response remains `200`. Untouched **on endpoints
that do support a filter**: a filter in a single form whose every bracket condition reached the
parameter — all brackets or one JSON object — the named parameters `field`, `sort` and `start`, and a
repeated `?filter=a&filter=b`, where the parser keeps the last value — provided that value is
NOT empty. An empty `?filter=` standing last overwrites the previous condition, and such a
request is refused.

Two caveats, so that list reads honestly. On endpoints that have no filter at all, **any** meaningful
`filter` is refused, a single-form one included. And a request where the bracket form follows an
empty `?filter=` (`?filter=&filter[ID]=1`) used to answer `200` with the filter correctly applied,
and is now refused as well: parameter names in a query string carry no values, so an empty spelling
cannot be told apart from a non-empty one and both are refused — as has long been the case for
entity list requests.

**What integrators should do**

1. Collapse the filter into ONE form: either all brackets (`?filter[ID]=1&filter[STAGE]=NEW`) or one
   JSON object (`?filter={"ID":1,"STAGE":"NEW"}`) — on endpoints that accept both.
   ⚠️ [GET /v1/calls/statistics](/docs/telephony/analytics/statistics) accepts the BRACKET form
   only: a JSON object is forwarded to Bitrix24 as a string and does not become a filter. Do not
   send an empty `?filter=` next to bracket conditions, even if such a request used to work.
2. Drop bracket conditions nested deeper than two levels: the query-string parser does not assemble
   them, and they never reached Bitrix24 before either.
3. On endpoints that have no filter at all, switch to the named parameters the error text lists:
   `field` for task history, `sort` and `offset` for the list of universal lists.

No support window is provided: the lost half of the conditions cannot be recovered, and answering
`200` to a request whose filter was not applied is the very defect being fixed.

### FIX-0910-13: two spellings of one filter field no longer drop a condition in silence

**Before**

A field has several accepted spellings: the schema name and the Bitrix24-native name (`amount`
and `OPPORTUNITY` on deals), a different case. When two conditions in one filter resolved to the
same Bitrix24 filter name, the request **answered `200` through a defect** with a condition lost:
only one of the two applied, and which one was decided by the key order in the request. The
result looked filtered even though half of the filter never applied. That answer could not be
relied upon — the documentation never promised it. The same happened with two spellings of one
operator, with paired date field names (`updatedAt`/`updatedTime`, `createdAt`/`createdTime`, where an
entity declares one of the two and accepts the other as its alias) and with synonym pairs an entity
declares as separate names: on leads those are `amount`/`opportunity`, `stageId`/`statusId`,
`currency`/`currencyId`.

**After**

Such a request is rejected with `400 INVALID_DUPLICATE_FILTER_FIELD`, and `message` names both
conditions and the shared name they resolved to. A request with a single spelling of the field
answers HTTP 200 as before — the successful response is preserved. Operators producing DIFFERENT
names are unchanged too: `{ "amount": { "$gte": 1000, "$lte": 5000 } }` is a range, not a
duplicate.

The result decides, not the spelling, and the two pairs have DIFFERENT boundaries. The `UF_` form
together with the camelCase spelling of the same custom field folds into one name only on entities
with the older naming style AND only for the spelling the platform converts: the
letters-only one (`ufCrmProjectCode`) on every such entity, and the digit-suffixed one
(`ufCrm_1698325419`) on requisites only. On the other entities a digit-suffixed pair travels as two
names, is not refused, and Bitrix24 still drops the second condition silently. On camelCase-named entities and on CRM
items those are different names on the wire and nothing is refused either. `$ne` together with `$nin` on one field
produces a single `!` prefix on EVERY entity except CRM items — including camelCase-named ones
(catalog products, mail mailboxes, smart processes); on CRM items `$nin` gets its own `!@` prefix
and the pair passes.

The rule applies to lists, search, aggregation and both batch doors; inside a batch only that
call is rejected and its neighbours still run.

**Affected endpoints:** `GET /v1/{entity}` and [POST /v1/{entity}/search](/docs/filtering#search-endpoint)
on entities with the standard filter parsing, `POST /v1/{entity}/aggregate` and its legacy twin
`GET /v1/{entity}/aggregate`, [POST /v1/batch](/docs/batch) and the `POST /v1/{entity}/batch`
sub-calls, plus `GET /v1/addresses` with `POST /v1/addresses/search`. On these doors the filter is
parsed by the same code, so the refusal arrives identically.

Individual routes with their own filter format parse it with their own code and are NOT part of this
change: there a pair of spellings still answers `200` with a condition lost. Those include
`GET /v1/requisite-links` with `POST /v1/requisite-links/search`, call statistics, lists, task
comments and the open-lines routes; the list is not closed.

Why this is a correction and not a contract change: the former `200` on such a request was never
promised by the documentation and was not reproducible — it applied one of the two conditions,
and which one was decided by the key order. That answer could not be relied upon, so there is no
support window for the old behaviour: there is nothing to support. A request with a single
spelling of the field is unaffected.

### NEW-0910-14: vibe balances for every Bitrix24 account in one export

A new `GET /v1/platform/revenue/balances` returns one row per Bitrix24 account, as a snapshot taken at read time. Each row carries `balance` (the same remainder the account sees), its composition `paidRemaining` + `grantRemaining`, the accrued shortfall `accruedShortfall`, `overdraftLimit`, `billingMode`, `frozen`, plus `portalId`, `portalDomain`, `portalStatus`, `portalKind` and `accountId`. The figures reconcile: `balance = paidRemaining + grantRemaining − accruedShortfall`.

The request takes no time window, only `limit` and `cursor`; the snapshot time arrives as `capturedAt` next to `data`. An account with no tranches is returned as a zero row instead of being skipped, so the export describes the whole account population. This is the only surface exposing the granted remainder: `GET /v1/platform/revenue/topups` covers purchased tranches, so an account with no purchases is absent from it and the welcome allocation and granted bonuses are not shown.

The method is opened by a dedicated `revenue:balances` scope — a key holding `revenue:read` gets `403 INSUFFICIENT_SCOPE` on it. The existing exports are unchanged and keys with existing scopes keep working with no edits.

### NEW-0910-15: companies expose their linked contacts through `include=contact`

The company entity declares a `contact` relation, so `include=contact` is now accepted on `GET /v1/companies/{id}`, `GET /v1/companies` and `POST /v1/companies/search`. Every record carries `_included.contacts`, an array of full contact records, each extended with the binding fields `sort`, `isPrimary` and `roleId`; a company with no linked contacts gets an empty array. Such a request used to answer `400 INVALID_INCLUDE`, because companies declared no relations at all and the list of available `include` values was empty.

The relation also opens the binding sub-routes: `GET /v1/companies/{id}/contacts` lists the bindings, `POST /v1/companies/{id}/contacts` adds one, `PUT /v1/companies/{id}/contacts` replaces the whole set, `DELETE /v1/companies/{id}/contacts/{contactId}` unlinks one. Requests without `include` answer exactly as before.

### FIX-0910-16: a boolean userfield flag is no longer dropped silently

**Before**

The userfield properties `multiple`, `mandatory`, `showFilter`, `showInList`, `editInList`
and `isSearchable` are stored by Bitrix24 as the characters `Y` and `N`. Sent as booleans,
they applied only half the time — within ONE request body: `showInList: true` and
`editInList: true` were switched on, while `showFilter: true` and `isSearchable: true` were
silently saved as off. The response was a success either way: `201` on create,
`{"updated": true}` on update. Re-sending the same flags as booleans changed nothing: the ones
that were off stayed off.

None of the six flags were declared in the API description: create listed only `fieldName`,
`userTypeId` and `label`, and update described its body as an empty object. There was nowhere
to learn the working value form.

**After**

All six flags are accepted both as booleans and as the strings `"Y"` / `"N"`; the platform
converts either form before calling the account. The canonical forms remain a boolean and
`"Y"` / `"N"`; on top of those the platform accepts the lenient spellings `"y"`, `1`, `"1"` to
switch a flag on and `"n"`, `0`, `"0"` to switch it off. All of them used to travel to the
account verbatim — the **enabling** ones did not switch the flag on and now they will; the
disabling ones switched it off before and still do, so nothing changes for them. Unrecognised
values pass through unchanged, as before.

**Take note if your integration already sends these flags as booleans.** Some of them used to
do nothing and you may have grown used to that — now all of them work. Two switch-ons were
measured: `showFilter: true` and `isSearchable: true` used to leave the flag off and will now
turn it on. Enabling `isSearchable` is not cosmetic: the field's values go into the account's
full-text search and start surfacing in global search.

Watch `mandatory: true` as well — once on, it blocks creating records without that field,
including through other integrations on the account. Whether it took effect from a boolean
before this change was not measured, so assume it will take effect now.

If your integration sends `showFilter: "E"` — the earlier table on the fixed-CRM pages called
that a "mask" — the
behaviour changes: such a write used to leave the filter OFF, and now it turns it ON. Among the
MEASURED string values this is the only switch-over, and it is deliberate: `"E"` is the form the filter
arrives in on a read, and sending it back must be safe.

If your integration sends `"I"` or `"S"` following our earlier table, such a write does not
turn the filter on — neither before nor now. Send `true` to turn it on.

Check what you send before you upgrade.

The flags are now declared in the API description for both create and update. The reading
quirk of `showFilter` is documented separately: an enabled filter comes back as `E`, which is
Bitrix24's storage form rather than an error. The values `"I"` and `"S"` are not accepted on
write and switch the filter off, so enable it with `true`, `"Y"` or an `"E"` you read back. The
fixed-CRM reference used to describe those letters as filter modes and used them in its
examples; a measurement across four field types did not confirm that, and the pages are
corrected.

The round trip is closed separately: an `"E"` you read can now be sent straight back — the
platform reads it as "on". Previously the ordinary read-modify-write cycle handed that `"E"`
back to the account and the filter switched off silently under a success response.

The change covers the fixed CRM types — deals, leads, contacts, companies, quotes and
requisites. Smart-process custom fields travel a different Bitrix24 contract and are not
affected by this change.

### FIX-0910-17: V1 responses return X-Request-Id

**Before**

The documentation asked clients to include `X-Request-Id` in support tickets, but API responses did not contain this header.

**After**

Every `/v1` response produced by the backend contains a server-generated `X-Request-Id`. The value from a failing response can be included in a ticket together with the request time.

**Impact on integrators**

Existing requests require no changes. A client may save the new header for diagnostics but does not have to process it.
