# API changes: September 23, 2026

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

### NEW-0923-1: ERP connection management through the Cowork API

On the RU platform, the Vibecode API adds 20 `/v1/cowork/onec/*` methods for connection status, administrator eligibility, credential issuance and revocation, catalog refresh, and access management. The international platform exposes only the safe status operation, which reports that ERP management is unavailable in that segment. An older Cowork key can request the required scopes through `/v1/connect/device/authorize` and `/v1/connect/token`.

The `listTools` result now also supports format `"2"`, which carries each tool's complete normalized input schema. Format `"1"` remains unchanged for stored operations and existing clients.

On the RU platform, the surface starts disabled and is enabled separately for each account after server readiness checks. The international status remains `UNAVAILABLE`. The change does not add a user interface and does not imply that the methods are already enabled for accounts.

### NEW-0923-2: turning chat notifications off and on

The new endpoint [POST /v1/chats/:chatId/mute](/docs/chats/management/mute) turns chat notifications off (`"mute": true`) or back on (`"mute": false`). The setting is personal: it changes for the user the call is made on behalf of. To change it for an employee who opened your application, call the endpoint with the application key and that employee's session. The `mute` field is required, and a request without it is refused with `400 MISSING_PARAMS` before the Bitrix24 call.

### BC-0923-3: creating a deal or a lead with a stage outside the dictionary no longer answers with success

> Old format supported until: not provided

**Before**

[POST /v1/deals](/docs/entities/deals/create) and [POST /v1/leads](/docs/entities/leads/create) with a stage the account's dictionary does not hold answered `201`: Bitrix24 accepted the write and silently put the record on the default stage. A typo in `stageId`, a different letter case, extra spaces, another pipeline's stage without its `categoryId` and a deal stage in a lead's `statusId` all behaved this way. The caller saw success while the record sat where nobody asked. The other doors — `PATCH`, `/move`, batch writes and import — already refused such a stage.

**After**

Before the write the stage is checked against the account's dictionary literally, the way Bitrix24 itself checks it: for a deal — against the stages of the pipeline named by `categoryId` (without it — the main one, `DEAL_STAGE`; for another pipeline — `DEAL_STAGE_{categoryId}` with stages like `C{categoryId}:NEW`), for a lead — against the `STATUS` statuses. A stage missing from the dictionary answers `400 UNKNOWN_STAGE` and no record is created. `message` names the key and the dictionary and suggests the closest exact spelling or the `categoryId` that must accompany another pipeline's stage; `details.knownStages` lists the dictionary's stages (up to fifty), `details.entityId` says which dictionary to read via `GET /v1/statuses?filter[entityId]=…`. Any spelling of the key Bitrix24 reads as `STAGE_ID` or `CATEGORY_ID` (raw `STAGE_ID`, `stage_id`, `StageId`) is checked the same way; when several spellings are sent, the last one in the body counts and the error names it in `details.field`. A list or an object in place of a stage name under a raw spelling of the key answers `400 UNKNOWN_STAGE` too, because Bitrix24 would put the record on the default stage (under `stageId` / `statusId` the shape check refuses it earlier — `400 INVALID_PARAMS`); that refusal needs no dictionary: `details.knownStages` is empty and `details.stageId` names the shape of the value. An empty stage (`""`, `null`) is still not checked — the record lands on the default stage. The dictionary is read once per account and pipeline and kept for five minutes, so an ordinary create did not get more expensive; if the dictionary cannot be read or comes back incomplete, the write proceeds as before. A stage from the dictionary answers as it did.

**What integrators should do**

Make sure the stages in your requests match the dictionary letter for letter: `GET /v1/statuses?filter[entityId]=DEAL_STAGE` for the main deal pipeline, `DEAL_STAGE_{categoryId}` for the others, `STATUS` for leads. Send another pipeline's stage together with its `categoryId`. Handle `400 UNKNOWN_STAGE` — repeating the request with the same stage is pointless, pick one of `details.knownStages`.

### BC-0923-4: CRM_CREATE in open-line configurations now accepts only supported values

> Old format supported until: not provided

**Before**

[POST /v1/openline-configs](/docs/openlines/config/create) and [PATCH /v1/openline-configs/:id](/docs/openlines/config/update) returned `200` for `contact`, `company`, and other unsupported `crmCreate` values, after which Bitrix24 stored the mode as `lead`.

**After**

`crmCreate` and its accepted key-name variants now allow only the exact strings `none`, `lead`, and `deal`. Any other value returns HTTP `400` with code `VALIDATION_ERROR` and a message naming the field and the complete set of allowed values. The request is rejected before the configuration is changed.

**What integrators should do**

Send only `none`, `lead`, or `deal`. Clients that sent `contact` or `company` must select a supported mode and handle `400 VALIDATION_ERROR`.

### FIX-0923-5: Field-name validation when reading requisites

**Before**

Some nonexistent field names, including `constructor`, `__proto__`, and `toString`, incorrectly passed requisite filter and sort validation.

**After**

Field validation now rejects inherited names before calling Bitrix24: with `400 UNKNOWN_FILTER_FIELD` for filters and `400 UNKNOWN_SORT_FIELD` for sorting. Request processing order is unchanged: body parsing may reject a request before field validation, and the `order` object still takes precedence over `sort`. Valid fields and their supported aliases work as before.

**Affected endpoint:** [GET /v1/requisite-presets/:presetId/fields](/docs/entities/requisite-presets/preset-fields/list).

**Impact on integrators**

Requests using valid field names do not need to change.

### FIX-0923-6: Reuse the application server after rebinding its key

**Before**

For applications with a previously lost key binding, a repeated `POST /v1/infra/servers` could attempt to create another server. A rebind conflict could leave the server and its application record linked to different keys.

**After**

A rebind conflict preserves the previous server and application binding in full. Creation recognizes a single eligible application server already managed by the current key and restores an empty binding. The reuse response remains HTTP 201 with `reused: true`. A concurrent binding change returns HTTP 409 `APPLICATION_REUSE_CONFLICT` with `retryable: true`; the request can be retried. `GET /v1/me` exposes optional `deployment.standalone.reuseTarget` and a checklist for the existing server without changing bindings. `TRIAL_PORTAL_LIMIT` carries `retryable: false` and guidance that avoids repeated creation.

### FIX-0923-7: Bracket form of `order` and `scope` no longer causes an internal error

**Before**

A request with a bracket-form parameter, such as `?order[x]=1` on [GET /v1/tasks/:taskId/history](/docs/task-history) or `?scope[x]=1` on `POST /v1/pages/:id/publication`, answered `500` instead of a regular response.

**After**

[GET /v1/tasks/:taskId/history](/docs/task-history) treats such an `order` as an unknown value and sorts ascending, as without the parameter; the response remains HTTP 200. `POST /v1/pages/:id/publication` rejects such a `scope` with `400 INVALID_SCOPE` and shows the received value in the error message, as for any other invalid `scope`.

**Impact on integrators**

Requests with string `order` and `scope` values do not need to change.

### FIX-0923-8: galaxy app diagnostics now check the public address

**Before**

The `reachability` block of `GET /v1/infra/servers/:id` derived its `effectiveStatus` verdict from
the container and the routing on the galaxy alone. Both facts are measured on the host, while the
last leg of the path to `https://app-XXXX.vibecode.bitrix24.com` is the app's tunnel at the
gateway, which is invisible from the host: a started routing unit means it was started, not that it
reached the gateway. An app whose public address answered nothing from outside could therefore be
reported as `"effectiveStatus": "running"`.

**After**

The verdict accounts for the public entrance, and a new field `reachability.publicEntry` ships
beside it — `live` when the gateway holds a live tunnel for the app subdomain, `no-tunnel` when
there is none, and `unknown` when the gateway's state could not be read. An app with a live
container and active routing but no tunnel answers `"effectiveStatus": "unreachable"`. The value
`unknown` never worsens the verdict: "could not look" is not "nothing works". The other
`effectiveStatus` values and every previous field of the block are unchanged, and no request needs
editing. The values are explained in [Galaxy app sleep and wake](/docs/infra/galaxy-sleep).

### FIX-0923-9: revoking server access now ends sessions that are already open

**Before**

A user who had opened the application the ordinary way received a browser session, and from
then on it lived on its own: removing their access-list entry
([DELETE /v1/infra/servers/{id}/access/{accessId}](/docs/infra/access/access-delete)) did not affect it.
The same tab kept opening the application, and the session extended itself while the user stayed
active — so access persisted indefinitely. A refusal appeared only on a fresh sign-in.

**After**

The right to enter is re-checked for an already-open session too. Once access is removed — by
the user entry, by department, or by changing the policy — the application stops opening within
a minute, and the session no longer extends itself.

Owner-only mode is the exception: some already-open sessions live out their own term, up to 40
minutes, and can no longer extend themselves. Count on that term if you switch a server to that
mode to close access immediately.

**Integrator impact**

No action required. Share-link access is unchanged: it does not follow the server access list
and is revoked separately, by revoking the link itself.

### FIX-0923-10: Legacy GET aggregates report incomplete data

**Before**

`GET /v1/{entity}/aggregate` could return HTTP 200 with numbers computed over only some records without indicating incompleteness.

**After**

The response remains HTTP 200. Its existing numeric fields and grouping are unchanged. `data.meta` always contains `recordsProcessed` and `truncated`. A known overall record count adds `totalRecords`, a positive shortfall adds `recordsShortfall`, and a failed page adds a sanitized `pageErrorSample` containing `code` and `message`.

A partial result includes `truncated: true` in `data`, each `data.groups` element and `data.total` when grouped. A group marker describes the incomplete overall selection, not proven missing rows in that specific group. `data.meta.warnings` includes code `AGGREGATE_TRUNCATED`. Complete responses omit conditional markers and the warning, with `data.meta.truncated` set to `false`.

When the overall count is unknown, `totalRecords` and `recordsShortfall` are omitted. An explicit incompleteness signal is retained even without a quantitative estimate of missing records. The `count` operation on this GET path also counts fetched records and can be partial. The existing HTTP 422 refusal above 10,000 records and deprecation headers are retained.

**Impact on integrators**

Check `data.meta.truncated` before treating numbers as final. Narrow the filter for partial results. The POST aggregation contract is unchanged.

### BC-0923-11: fieldName is checked before Bitrix24 is called when creating a custom field

> Old format supported until: not provided

**Before**

[POST /v1/userfields/:entity](/docs/userfields/crm/create) forwarded `fieldName` to Bitrix24 exactly as received, without checking it. An empty string, `null`, a number, an array or an object reached Bitrix24, and what happened next was decided there: an empty name came back as an error with no code — `422 BITRIX_ERROR` with `b24Code: "0"` on the way out, which tells nothing about what the request got wrong — while a non-string value could be coerced into a string and create a field with a garbage name under a success answer.

**After**

The value is checked before Bitrix24 is called: it must be a string carrying at least one non-whitespace character, otherwise the refusal is `400 INVALID_FIELD_NAME`. What is checked is the value that actually leaves for Bitrix24 — when the body carries both spellings, `fieldName` and its raw counterpart `FIELD_NAME`, the last one is what travels, and that one is judged. The same rule now covers the mandatory `userTypeId`, and for a body carrying ONE spelling nothing changed: a missing or empty type is refused `400 MISSING_FIELD` exactly as before. The difference is a body carrying both spellings at once — the first non-empty one used to be judged, now it is the one that travels to Bitrix24. The body `{"userTypeId": "string", "USER_TYPE_ID": ""}` used to reach Bitrix24 and return its opaque answer, and now gets `400 MISSING_FIELD`; the opposite body `{"userTypeId": "", "USER_TYPE_ID": "string"}` used to be refused `400 MISSING_FIELD`, and now creates a field of type `string`. The name format still belongs to Bitrix24: the `UF_CRM_` prefix, the length and the character set are its checks.

**What integrators should do**

Calls with a valid `fieldName` are untouched, and so are calls without the key at all — there Bitrix24 generates the field name. Code changes are needed where `fieldName` carried a non-string, an empty string or a whitespace-only string: such a call now answers `400 INVALID_FIELD_NAME` instead of its previous answer. A name taken from untrimmed user input or from a spreadsheet cell falls under this too — trim it on your side. To have the platform generate a name, the key is omitted entirely: an empty string and `null` do not serve that purpose.

### NEW-0923-12: server detail returns the last deploy's start command, install command and port

The [GET /v1/infra/servers/:id](/docs/infra/servers/get) response gains three fields: `startCommand`, `installCommand`, `deployPort` — a
snapshot of `start`/`install`/`port` from the body of the last SUCCESSFUL
[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy). For a server with no successful deploy yet, all
three fields return `null`. A development-team member (`access.via: "collaborator"`) receives these fields exactly as the owner does.

### NEW-0923-13: app active-servers refusal now names the server owner

In the `409 APP_HAS_ACTIVE_SERVERS` response of [DELETE /v1/apps/:id](/docs/apps/delete), every element of `details.servers` gained an optional `ownerName` field — the name of the server owner, or `null` when the server has no owner. Until now the response gave no way to tell whose server was blocking the app deletion, and the owner had to be looked up by hand. The other fields of the element and the error code are unchanged, so requests from existing clients keep working.

### BC-0923-14: retrying Cowork application creation after a network failure returns the first attempt's outcome — while it is still fresh

> Old format supported until: not provided

**Before**

[POST /v1/cowork/applications](/docs/cowork/applications-create) permanently claimed the Idempotency-Key after a failure during key issuance (for example, Bitrix24 Network being unreachable while the Bitrix24 account was already contacted): a retry with the same `Idempotency-Key` answered a generic `409 IDEMPOTENCY_KEY_ALREADY_USED` regardless of what actually failed, and the draft application could not be recovered — but a retry with the same key was guaranteed to never create a duplicate: it either replayed into the 409 or never went through at all.

**After**

Such a retry, within 20 minutes of the first failure, now gets the SAME response the first attempt got — same HTTP status, same `error.code`, same body — with an added `Idempotent-Replayed: true` header. After 20 minutes the Idempotency-Key is forgotten and a retry with it runs as a brand-new attempt, every check re-evaluated from scratch. If the first failure happened AFTER Bitrix24 was contacted (for example `502 BITRIX_UNAVAILABLE`), the account may already have received a webhook from the first attempt; a same-key retry past the 20-minute window does not check for that and can mint a SECOND key and create a second application on top of the first. `409 IDEMPOTENCY_KEY_ALREADY_USED` now means only "the key belongs to an application that has since been deleted" — a permanent refusal, as before.

**What integrators should do**

Clients will no longer see `IDEMPOTENCY_KEY_ALREADY_USED` for the "first attempt reached Bitrix24 and failed there" scenario: within a 20-minute window they instead get the original failure (e.g. `502 BITRIX_UNAVAILABLE`) with `Idempotent-Replayed: true`. Retry with the SAME `Idempotency-Key` only WITHIN that window — replay there is deterministic and safe. More than 20 minutes after the first failure, changing the `Idempotency-Key` value does not protect against a duplicate: retrying with the old key or a new one both run as a genuinely new attempt, and if the first failure happened after Bitrix24 was contacted, it can mint a second key on top of the first. Usually there is nothing on the platform to check either — the failed reservation is removed together with its outcome, and most failures at this stage never mint a key locally; look for a duplicate on the Bitrix24 account itself if you can, and treat the risk as inherent past the window. The exception is `500 APPLICATION_CREATE_FAILED`: if the key was already minted and only saving the card failed, that stray key can be found and revoked in the platform's Keys section; an empty list means there is nothing to clean up. No change to how `error.code` is branched on; do not rely on `IDEMPOTENCY_KEY_ALREADY_USED` staying permanent for failures after the Bitrix24 account was contacted.
