# API changes: September 7, 2026

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

### FIX-0907-1: redeeming a coupon on your own Bitrix24 account is now refused with a dedicated code

**Before**

`POST /v1/cowork/coupon/redeem` looked only at the coupon, the campaign and the seat. It did not
care who was redeeming or whose account it was: a platform employee holding the coupon-issuing
right could mint a code and activate it on an account they stand behind — the plan was granted at
the platform expense, and the response was a plain `HTTP 200`.

**After**

Redemption is refused with `COUPON_SELF_PORTAL` (`HTTP 409`) when the account is backed by a
platform employee holding the coupon-issuing right — the redeemer themselves, or any account
member, API key owner or server owner on it. A successful redemption is not affected: on an
account with no such people the HTTP 200 response is unchanged, body included, and no integration
work is required. The refusal does not consume an attempt against the code-guessing counter —
the cause is the account's composition, not a wrong guess. The code is listed in the endpoint's
response table in the documentation.

### NEW-0907-2: self-description now says whether infrastructure is stopped for non-payment

The [GET /v1/me](/docs/keys-auth/me) and [GET /v1/cowork/state](/docs/cowork/state)
responses carry a new `infraState` block: `frozen` tells whether servers, deploy and
storage are stopped for non-payment, `reason` is the reason code (`DEBT` or `null`), and
`topupUrl` is the dashboard top-up address or `null`.

The block answers a question that had no readable source before: what exactly is stopped
for an account in debt. The strings inside the block are machine-readable, and the
human-facing text is up to the client.

Note that what the field means depends on whether infrastructure-debt scoping is enabled
for the account (the `FIX` in this same release). Until it is, a negative balance refuses
V1 calls broadly, `GET /v1/cowork/state` included; only `GET /v1/me` stays readable in that
mode, being exempt from the freeze. There `infraState.frozen` means "almost everything is
stopped". Once it is enabled the field means exactly what it
says: infrastructure is stopped while calls within the plan monthly quota keep working.

The block is additive, existing fields are unchanged.

### FIX-0907-3: fixed pagination for business process activity and automation rule lists

**Before**

A request with `offset=50` returned the same codes as a request with `offset=0`. The response remained successful with HTTP 200.

**After**

`offset` skips the specified number of codes in the full list. `meta.total` reports the full list size, while `meta.hasMore` reports whether codes remain after the current page. The same behavior applies to list sub-calls for these entities in the global batch request, including both total channels. The response still returns HTTP 200, and OAuth authorization requirements are unchanged.

**Impact on integrators**

No action is required. Integrations using `offset` now receive the requested page instead of a repeated first page; the response format and authorization requirements are unchanged.

**Affected endpoints:** [GET /v1/bizproc-activities](/docs/entities/bizproc-activities/list), [GET /v1/bizproc-robots](/docs/entities/bizproc-robots/list), [POST /v1/batch](/docs/batch).

### FIX-0907-4: infrastructure debt no longer disables what is paid for separately

**Before**

A negative wallet balance returned 402 `ACCOUNT_FROZEN` on almost every V1 call — only
self-description, the guide and the support conversation were exempt — including calls
that never touch the wallet: proxying to your own Bitrix24, Cowork subscription
state, the model list. Debt for a virtual machine disabled AI granted by the Bitrix24
plan.

**After**

The account freeze applies to what the wallet pays for: servers, storage, deploy, search
and research on platform credentials, spending beyond the monthly quota, deploy-key
issuance and application creation. A call within the plan monthly quota, a paid Cowork subscription period and REST proxying
to your own Bitrix24 account go through whatever the wallet state is.

Note the behaviour sits behind the `wallet-debt-scoped-to-infra` feature flag; with the
flag off the responses are unchanged.

### FIX-0907-5: an oversized numeric CRM ID is rejected before Bitrix24 is called

**Before**

Numeric CRM IDs were checked for shape only: any digit string without leading zeros passed the guard. This affected regular record `:id` values in paths (`/v1/deals/{id}`, `/v1/contacts/{id}` and similar entities), as well as positive smart-process `entityTypeId` values and related CRM operations. An ID beyond the safe integer range reached Bitrix24, where it could be rounded to a different value and some `crm.item` methods answered it with a raw PHP error instead of a clear refusal.

**After**

Such IDs are rejected as invalid parameters before Bitrix24 is called. The check covers regular record IDs on path and batch surfaces, as well as smart-process `entityTypeId` values, dynamic parameters and related CRM operations. The boundary is the safe integer: `9007199254740991` is still accepted, while `9007199254740992` and anything longer is rejected. The ID `0` remains valid wherever it was already allowed, such as the main deal pipeline.

### FIX-0907-6: lead conversion respects the selected deal pipeline

**Before**

`POST /v1/leads/{id}/convert` ignored the `categoryId` parameter, so the new deal was placed in the default pipeline.

**After**

When `categoryId` is provided, the new deal is created in the selected pipeline immediately. The value `0` still selects the default pipeline.

A non-negative integer string remains accepted for compatibility and is normalized to a number. Other value shapes remain ignored and leave the deal in the default pipeline; `null` and an omitted parameter are equivalent.

### NEW-0907-7: Bitrix24 employee id of the app author in /v1/apps responses

Responses of the apps family now carry two new fields: `authorBitrixUserId` — the
numeric Bitrix24 employee id of whoever created the app — and
`authorBitrixUserIdSource`, telling where that id came from. `authorBitrixUserId`
is the same identifier the `id` field of [GET /v1/users](/docs/entities/users/list)
returns, so it is the join key between the two responses: an app previously carried
only `authorId`, a Vibecode platform user identifier, with nothing to match the
creator against an employee card.

Values of `authorBitrixUserIdSource`: `member` — the id comes from the author's
confirmed membership of this Bitrix24 account and is safe to link; `snapshot` — the
id comes from a value captured when the app was created, which is best-effort and
may point at a different employee than the app's current author; `null` — the id is
unknown, and `authorBitrixUserId` is `null` as well. A registry that must not be
wrong should link to an employee card only on `member`. On self-hosted accounts and
on accounts with microservice credentials the `snapshot` value is never returned:
there the captured value has no identity-confirmed origin. Confirmed membership
still works on such accounts, so the id there is either `member` or empty.

The author's name is deliberately NOT returned by the apps responses, which narrows
the original request — it asked for the name as well. Personal data does not travel
in a response every account key can read, the key embedded in a deployed
application's code included; the name is fetched by `authorBitrixUserId` from
[GET /v1/users](/docs/entities/users/list), where the `user` permission gates it.

Existing requests keep working unchanged: the fields are additions, nothing was
removed or renamed.

**Affected endpoints:** [GET /v1/apps](/docs/apps/list),
[GET /v1/apps/:id](/docs/apps/get), [POST /v1/apps](/docs/apps/create),
[PATCH /v1/apps/:id](/docs/apps/update),
[POST /v1/apps/:id/publish](/docs/apps/publish),
[POST /v1/apps/:id/unpublish](/docs/apps/unpublish),
[POST /v1/apps/:id/relink-oauth](/docs/apps/relink-oauth)

### BC-0907-8: companies field removed from the contacts API

> Old format supported until: not provided

**Before**

The `companies` field was advertised by [GET /v1/contacts/fields](/docs/entities/contacts/fields) and accepted in `select` for contact read operations. Bitrix24 did not return a value for this field, so a successful response could omit the `companies` key.

**After**

The `companies` field is no longer advertised and is always removed from contact responses. Requesting it in `select` is rejected with `400 UNKNOWN_SELECT_FIELD`.

**What integrators should do**

Do not request `companies`. Use the `companyIds` field for related company identifiers.

**Affected endpoints:** [GET /v1/contacts](/docs/entities/contacts/list), [GET /v1/contacts/:id](/docs/entities/contacts/get), [POST /v1/contacts/search](/docs/entities/contacts/search), [POST /v1/contacts/aggregate](/docs/entities/contacts/aggregate), [POST /v1/contacts/batch](/docs/batch), [GET /v1/contacts/fields](/docs/entities/contacts/fields).

### FIX-0907-9: multipart upload replaces a large object under its existing key

**Before**

[POST /v1/storage/objects/multipart/create](/docs/storage/upload/multipart-create) returned `409 STORAGE_KEY_EXISTS` when a live app object already occupied the same `key`. Content larger than 10 MB therefore could not be replaced without changing the key. For a personal key, the advice to use multipart led to the same failure.

**After**

For an existing app object, the request returns `200` and opens a replacement session with the existing `objectId`. Reads return the old content until [POST /v1/storage/objects/multipart/complete](/docs/storage/upload/multipart-complete) publishes the new bytes; a successful response confirms the new version. A confirmed [POST /v1/storage/objects/multipart/abort](/docs/storage/upload/multipart-abort) before publication preserves the old version. If complete returns `502 STORAGE_BUCKET_ERROR`, publication may have happened: that response does not identify the current version, the session remains active, and object deletion stays blocked until a platform administrator resolves it. Object visibility does not change.

[POST /v1/storage/objects/multipart/create](/docs/storage/upload/multipart-create) returns `409 STORAGE_KEY_EXISTS` for an existing live object owned by a personal key and does not open an upload session. Delete the object first, then start a new multipart upload under the same `key`. This operation is non-atomic: the object is unavailable between deletion and successful completion of the new upload.

**Impact on integrations**

App files larger than 10 MB can be updated by multipart upload under the existing `key`. Multipart create, complete, and abort may return the retryable `409 STORAGE_KEY_CONFLICT` when another request is changing the same `key` or session state changed. For create and complete, retry the same operation. A `409` from abort during finalization requires retrying complete with the same `parts`; if complete returns `502 STORAGE_BUCKET_ERROR`, do not infer the current version and contact a platform administrator. If abort returns `502 STORAGE_BUCKET_ERROR`, keep retrying abort until session release is confirmed. [DELETE /v1/storage/objects/{key}](/docs/storage/objects/delete) returns `409 STORAGE_MULTIPART_IN_PROGRESS` while a multipart session is active: complete or abort the session first, accounting for the finalization case above.

### FIX-0907-10: moving a deal/lead to a nonexistent stage now returns an error instead of a silent success

**Before**

`POST /v1/deals/{id}/move`, `POST /v1/leads/{id}/move`, `PATCH /v1/deals/{id}` and `PATCH /v1/leads/{id}` answered `200 success:true` with the full deal/lead object even when the requested `stageId` (or a deal's `categoryId`) did not exist in the reference list — Bitrix24 silently ignored the value, the stage stayed unchanged, and the caller could not tell a real move from a rejected one without manually comparing the `data.stageId` field.

**After**

The response remains `200` when the requested stage or pipeline exists and is applied — behavior for valid values is unchanged. When the requested value was not applied (Bitrix24 accepted the call but the actual stage stayed the same), the response is `422 STAGE_NOT_APPLIED` with details in `error.message`: what was requested and what is actually there after re-reading the record.

### FIX-0907-11: The placement-bind note says trial, not demo

**Before**

The `apps.bindPlacements` capability note in [GET /v1/me](/docs/keys-auth/me) still called Bitrix24 trial
access a demo, while the refusal it describes has always been named after a trial.
FIX-0904-7 settled the latin terminology on the word trial everywhere else, so this
string was the last live disagreement of its kind. The same term also survived in two
documentation articles — the one on starting Bitrix24 trial access, and the plan name
reported by `GET /v1/me`.

**After**

The note says trial, and the documentation uses the same term. Refusal codes, statuses
and response fields are unchanged, and a successful response stays successful. The note
is not localised and is served identically on every installation.

**Impact on integrators**

None for logic. A client matching this note by substring should re-check the comparison.

### FIX-0907-12: the spec declares every server mode-switch response

**Before**

The public spec `GET /v1/openapi.json` declared only `200`, `400`, `403` and `404` for [PATCH /v1/infra/servers/:id/mode](/docs/infra/access/mode). A client generated from the spec treated the remaining outcomes as impossible, although the endpoint returned them: the exhausted-balance refusal, the server-role refusal, a parallel-request conflict and two network-policy failures.

**After**

The spec gains the responses the endpoint does return: `401`, `402` `ACCOUNT_FROZEN` and `OPEN_MODE_REQUIRES_COMMERCIAL`, `409` `SERVER_NOT_RUNNING`, `AGENT_NOT_CONNECTED`, `MODE_SWITCH_SEALED_ROLE` and `CONFLICT`, `502` `IPTABLES_FAILED`, `PROVIDER_NOT_CONFIGURED`, `GATEWAY_UNREACHABLE` and `TUNNEL_NOT_FOUND`, `503` `SECURITY_GROUP_ATTACH_FAILED` and a refusal whose code starts with `GATEWAY_TIMEOUT`. The `400` description gains the codes `SAME_MODE`, `NO_SUBDOMAIN` and `MODE_SWITCH_STANDALONE_ONLY`. The same codes now appear in the endpoint page's error table: before this change it named none of the three state refusals — server not running, agent not connected, server without a subdomain. The code `PROVIDER_ERROR` is removed from the endpoint page's error table: a mode switch does not return it. Status `429` is deliberately left undeclared — the endpoint carries no limiter of its own, and the platform-wide edge limit is documented on the [limits](/docs/errors/limits) page rather than declared per route. Endpoint behaviour is unchanged: the successful response remains HTTP 200 with the same body, no request needs changing, and the edit touches the declaration and the documentation only.

### NEW-0907-13: `/v1/cowork/me` and `/v1/cowork/state` now name the Bitrix24 account the key is bound to

Both Cowork/Code self-info endpoints gained a new `portal` field:

```json
{
  "portal": { "id": "8f3c…", "domain": "acme.bitrix24.com" },
  "tier": "PRO",
  "state": "ACTIVE"
}
```

The field answers which account the rest of the body is about. A Cowork/Code seat
is defined on the pair «key owner + account», and a key is bound to a single
account for good, so for a person with several accounts the app reports the tier
and quota of one account while the browser shows another. Until now the domain
arrived exactly once, when the code was exchanged for a key
(`POST /v1/connect/token`), and could not be asked for again.

`portal.id` is always present — it is taken from the key itself. `portal.domain`
is `null` only in the degenerate case where the account row is already gone. Every
other field of both responses is unchanged, and requests written before the field
existed keep working without edits.

### BC-0907-14: a partial lead conversion failure now answers 422, not 200

> Old format supported until: not provided

**Before**

[POST /v1/leads/:id/convert](/docs/entities/leads/convert) creates the deal, contact and company as separate records one after another. When only some of them were created, the response still arrived with status `200`: the `success` field was `false`, while `error`, `code` and `details` sat at the top level of the body, and `error` was a string rather than an object. A client branching on the HTTP status or reading `error.code` took a partially written CRM state for a success.

**After**

A partial failure answers `422` with code `CONVERSION_FAILED` in the usual error envelope: `error` is an object carrying `code` and `message`. The outcome of every requested operation moved into `error.details` — one key per deal, contact and company, each with a `success` flag and either the id of the created record or the Bitrix24 refusal text. The top-level `error` string and `code` are gone from the response. A lead that could not be read answers `404` with code `ENTITY_NOT_FOUND` in the same envelope. A successful conversion still answers `200` with a `data` field.

**What to do**

Branch on the HTTP status and on `error.code`, not on the `success` field and not on the top-level `code`; read per-record outcomes from `error.details` rather than from `details`. Records already created are still not rolled back and the lead keeps its status, so before repeating a call check what already landed in CRM: a repeat creates a second set of records. There is no support window for the former `200` response: it reported success where some CRM records had not been created, so keeping it would mean going on passing a failure off as a success.

### FIX-0907-15: root-type validation on entity create, update, batch and search

**Before**

A JSON literal `null` as the request body crashed `POST /v1/{entity}`, `PATCH /v1/{entity}/{id}` and `POST /v1/{entity}/batch` with `500 INTERNAL_ERROR`; that status was not declared for any of the three routes. A JSON array or a JSON string as the body of `POST /v1/{entity}` created a real record with empty or auto-generated fields and answered `201`, as if the request had been valid. On `PATCH /v1/{entity}/{id}` a JSON string passed the empty-body check and answered `200` without actually changing anything. On `POST /v1/{entity}/search` an array body answered `400 INVALID_FILTER_SHAPE` with a message that misnamed the type actually sent (reporting a function instead of an array); a bare scalar body (a number, a string, a boolean) was treated as "no filter" and answered `200` with the unfiltered record list.

**After**

A well-formed body (a JSON object — for `/search`, including an empty `{}`, which is the legitimate "no filter" request) is handled unchanged. The response still remains `201` on create and still remains `200` on update, batch and search — none of that changed. A body whose JSON root is not an object (`null`, an array, a string, a number, a boolean) now answers `400` on all four routes, and no record is created or modified: `EMPTY_CREATE_BODY` on create, `EMPTY_UPDATE_BODY` on update, `INVALID_BATCH_ACTION` on the per-entity batch route, `INVALID_REQUEST` on search. The search error message now names the type actually sent instead of an internal implementation artifact.

### FIX-0907-16: an absent record answers 404 for warehouses, catalogs, order statuses, basket items and smart processes

**Before**

One and the same scenario — "no record with this `id`" — answered with different codes inside a
single product area. `GET /v1/order-statuses/{id}` and `GET /v1/basket-items/{id}` returned
`422 BITRIX_ERROR`, while the neighbouring orders, invoices, payments and quotes returned `404` at
the same step. `GET /v1/catalogs/{id}`, `GET /v1/warehouses/{id}` and the product-property pages
behaved the same way, even though catalog products and prices already answered `404`. A
delete-confirmation check based on the response code silently failed on those paths.

Separately, on an account whose interface language is not Russian, `PATCH` and `DELETE` for
`/v1/smart-processes/{id}` with a nonexistent `entityTypeId` answered `400 INTERNAL_ERROR` — a code
the `DELETE` contract does not even declare. On a Russian-language account the same call correctly answered
`404 SMART_PROCESS_NOT_FOUND`, and `GET` was correct in every language.

**After**

An absent record answers `404` on all of the paths above. `GET`, `PATCH` and `DELETE` for
warehouses, catalogs, order statuses and basket items return `404 ENTITY_NOT_FOUND`; the Bitrix24
text in the `message` field is unchanged, only the classification of the response is. The same
`404` now arrives on the product-property and property-value pages.

`PATCH`, `DELETE` and `POST /v1/smart-processes/batch` answer `404 SMART_PROCESS_NOT_FOUND`
regardless of account language, exactly as `GET` has done for a long time. A client can rely on one
existence code across the whole Vibecode API instead of keeping per-area and per-language
exceptions.
