# API changes: September 2, 2026

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

### BC-0902-1: Documents expose the PDF transformation status

> Old format supported until: not provided

**Before**

The `isTransformationError`, `transformationErrorCode`, `transformationErrorMessage`, `transformationCancelReason`, and `pullTag` fields were not declared in the documents contract. If Bitrix24 returned them through [GET /v1/documents/:id](/docs/entities/documents/get), the Vibecode API passed their values without normalization. The same fields in [POST /v1/documents](/docs/entities/documents/create) and [PATCH /v1/documents/:id](/docs/entities/documents/update) bodies were not rejected by the local read-only field validation.

**After**

The five fields are declared as nullable response-only fields. `isTransformationError` is returned as `boolean | null`, and the other four fields are returned as `string | null`. Empty strings in the string fields become `null`. A write request containing any of these fields returns `400 READONLY_FIELD`.

**What integrators should do**

Do not send these five fields in document create or update bodies, including when resubmitting a previously received object. Accept `null` when reading the fields and use `pullTag` to subscribe to transformation status updates.

### BC-0902-2: The mode-switch address in the access-mode refusal is no longer fixed

> Old format supported until: not provided

**Before**

The `WRITE_BLOCKED_READONLY_KEY` access-mode refusal always returned `details.switchUrl` as
`"/keys"`, and the machine schema pinned that value with `enum: ["/keys"]`. The keys section lists
personal keys only, so for the two other kinds the path was a dead end: neither an application
auth key nor a management key is ever listed there, by construction — neither for the owner
nor for the Bitrix24 account administrator. The holder was given the address of a page that
carries no switch for their key.

**After**

`details.switchUrl` points at the page that switches the mode for that particular key: the
keys section `"/keys"` for a personal key, the Applications page `"/applications"` for an
application auth key, the management keys section `"/management-keys"` for a management key.
The message text names the same address as the field — they are built together and cannot
disagree. The value enumeration is gone from the schema: this is a UI
path, not a protocol constant, and pinning a literal made the contract unsatisfiable once the
page moved. The field is still always present and still required — the value changed, not its
presence.

**What integrators should do**

Read `switchUrl` from the response instead of hardcoding `"/keys"`. Clients that generate
types from the schema no longer receive the literal type `'/keys'` — regenerate your types,
otherwise value validation will fail.

### FIX-0902-3: a stream that never started ends with a retryable error for models from every provider

**Before**

Stream setup for `bitrix/*` models already made a single attempt on one shared budget, while for models from every other provider the platform silently repeated the request and waited for another full budget. The client received nothing meanwhile — up to two full budgets of silence — and then saw a generic provider failure that does not tell a stalled stream setup apart from any other unclassified error.

```
data: { "error": { "code": "ai_provider_unavailable", "type": "server_error", "retryable": true, "retryAfter": 6 } }
data: [DONE]
```

**After**

Stream setup makes a single attempt on one shared budget for models from EVERY provider. If the headers do not arrive within it, the wait no longer doubles and the stream ends with the same `stream_idle_timeout` event as a stream that went silent mid-response — naming the stream itself as the thing that stalled.

```
data: { "error": { "code": "stream_idle_timeout", "type": "server_error", "retryable": true, "retryAfter": 7 } }
data: [DONE]
```

The wait no longer doubles invisibly and the reason is named precisely — now across the whole model set, not only `bitrix/*`.

### NEW-0902-4: booking resources lookup in V1

[GET /v1/booking-resources](/docs/entities/booking-resources) is now available — it returns the booking resources available to the key and closes the gap in the first-booking flow: [POST /v1/bookings](/docs/entities/bookings/create) requires a non-empty `resourceIds`, and there was no way to learn valid identifiers through V1 alone.

The `booking` scope is required. A key in read-only mode works — the method is a read. The `typeId` and `searchQuery` filters are optional and may be combined, and any other parameter is rejected with `400 INVALID_PARAMS`.

The catalogue is collected in full, so there are no pagination parameters: `meta.total` equals the length of `data`, and `meta.hasMore` is always `false`. Each record carries exactly four fields — `id`, `name`, `typeId` and `isMain`. The `description` field is withheld: it is free-form text from the Bitrix24 account and is not needed to pick a resource.

Completeness is guaranteed for a catalogue that did not change during the request — Bitrix24 provides no snapshot of the collection. An account with more than 500 resources gets `502 BITRIX_RESULT_TOO_LARGE` instead of a truncated list; the same two filters narrow the lookup.

### NEW-0902-6: device-flow accepts a device identifier

`POST /v1/connect/device/authorize` accepts an optional `device_id` — a stable identifier of the client installation (up to 128 characters; letters, digits, `_.:-`). It takes effect for the Cowork application: when the same device connects again, the previous key is revoked instead of sitting in the list next to the new one. Other clients may send the parameter, but it does not yet affect how their keys are issued.

The parameter is optional, and behaviour without it is unchanged — every connection issues its own key and earlier ones stay active. Several distinct devices for one person keep working as before: their identifiers differ, so their keys never displace each other. A value that does not match the format is ignored — the connection still succeeds, only the replacement is lost.

### NEW-0902-7: new INT_BOX_PARTNER_REQUIRED refusal code for self-hosted accounts on the international installation

On the international installation platform access for a self-hosted account can require a
confirmed partner (NFR) licence. A self-hosted account without that confirmation receives
HTTP 402 with the new code `INT_BOX_PARTNER_REQUIRED` — on server, agent and managed-bot
creation, and on key issuance. The refusal body is shaped like its neighbours in this family
(`userMessage`, `alternatives`, `hint`), but `details.requiredTariffs` is empty: moving to
another Bitrix24 plan does not clear this refusal, and the mark is read when the connector
module registers. The same code appears in the `reason` of the `capabilities.servers.create` slot of
`GET /v1/me`, and the documentation address in its `alternatives[].url`. The requirement is switched on separately; while it is off, no account sees this code and responses stay
unchanged.

### NEW-0902-8: stable account identifier in the key self-description

[GET /v1/me](/docs/keys-auth/me) returns a new field, `portalId` — the stable identifier of the Bitrix24 account the key is bound to. It arrives in the same request, next to the existing `portal` and `owner`, and is present for `vibe_api_` and `vibe_app_` keys.

Unlike the domain in the `portal` field, this identifier survives a rename or a move of the account. Use it whenever local data is split per account: an account key built from the domain stops matching the previous one after a move, and the data of the same account ends up in a new empty store.

In a successful response the field is never empty: a key with no account bound never reaches this response, it gets `401 NO_PORTAL`. Management keys `vibe_live_` do not get the new field — they are not bound to a single account and still return the list of accounts in `portals`.

### NEW-0902-9: server creation answers 409 `REISSUE_IN_PROGRESS` while the application authorization key is being re-issued

`POST /v1/infra/servers` has a new refusal code `REISSUE_IN_PROGRESS` with status `409`. The Vibecode platform answers with it when the application card owner is re-issuing its authorization key at that moment: no server is created, because it would land on a key that is being revoked in the same second.

The refusal is transient — repeat the request once the re-issue finishes. The successful response and every other refusal code are unchanged, and requests that worked before keep working.

### FIX-0902-10: Open Channels dashboard methods are now available in OpenAPI

**Before**

Six public Open Channels statistics methods were described on documentation pages but were absent from `/v1/openapi.json` and the generated API cards.

**After**

All six methods are available in OpenAPI and API cards with the `imopenlines` scope, parameters, and response schemas. Runtime behavior is unchanged: a successful response remains HTTP 200, and the existing error statuses and schemas are preserved.

### NEW-0902-11: support code in an unrecognized key issuance refusal

When Bitrix24 refuses to issue a key for a reason the platform does not
recognize, the response now additionally carries `error.details.incidentCode` —
a six-character support code. The platform writes the same code to the account
journal next to the refusal breakdown, so quoting it is enough for support to
find the entry.

The field is optional and added to the existing response body: the HTTP status,
`error.code` and `error.details.reason` are unchanged, and clients need to do
nothing.

### NEW-0902-12: rate limit, ETag and Vary on GET /v1/openapi.json

[GET /v1/openapi.json](/docs/api-reference) now carries a rate limit per caller address. The precise cap is returned in the `x-ratelimit-limit` response header — read it there instead of hard-coding a value. Exceeding it answers `429 RATE_LIMITED`. The cap is generous and normal reading does not reach it: the spec is served with an `ETag`, so a discovery client or an SDK generator gets a `304` instead of the body on a repeat fetch.

The spec body can depend on the request's key. The response is therefore marked `Vary: Authorization, X-Api-Key` (the key is read from either header), and with a key the body differs for it is served as `Cache-Control: private` rather than `public`. Do not reuse one stored spec file across different keys.

### FIX-0902-13: the Bitrix24 plan refusal on key issuance and app installation now states its cause

**Before**

When Bitrix24 refused issuance with `FEATURE_NOT_AVAILABLE_ON_CURRENT_PLAN` (the Bitrix24 plan does not include Vibecode), the answer carried nothing actionable on ANY issuance endpoint — both where a key is issued ([POST /v1/keys](/docs/management-keys) → `502 CONNECTOR_KEY_ISSUE_FAILED`) and where an app is installed ([POST /v1/apps](/docs/apps/create) → `502 CONNECTOR_APP_INSTALL_FAILED`). The body held no human-readable text and the cause stayed in an internal field only, so clients read the refusal as a transient platform failure and retried. Retrying never helps: the gate lifts only when the Bitrix24 plan changes.

```
HTTP 502
{ "success": false, "error": { "code": "CONNECTOR_KEY_ISSUE_FAILED", "message": "connector refused to issue the key" } }
```

**After**

The refusal is now resolved against the account region. Where access is on sale, the answer is `402` with a plan paywall code and human-readable text: `INT_VIBE_PLUS_REQUIRED`, carrying `details.requiredTariffs: ["vibe+"]` and a `details.upgradeUrl` pointing at the plan page inside the Bitrix24 account itself. Where there is nothing to offer (a self-hosted portal, an account already on a paid plan, an unrecognised region), the answer stays `502` but gains its own code `CONNECTOR_PLAN_REQUIRED` and a non-empty `userMessage` instead of an empty body.

```
HTTP 402
{ "success": false, "error": { "code": "INT_VIBE_PLUS_REQUIRED", "message": "This action requires a Vibe+ plan on your Bitrix24 account.", "details": { "requiredTariffs": ["vibe+"], "upgradeUrl": "https://example.bitrix24.com/online/?feature_promoter=limit_why_pay_tariff_vibe" } } }
```

**What integrators should do**

Successful responses are unchanged. A client that branches on the error code now receives a terminal class instead of a transient one and can stop retrying: none of the new codes clears on a retry. A client that only looked at the status keeps working — the operation ended in an error before and still does.

### FIX-0902-14: a refusal on the consent page returns the user to the partner application

**Before**

The user approved access, hit a key issuance refusal — no subscription, an unsupported region, the key limit used up — and stayed on the consent page. Nothing arrived at `redirect_uri`: the partner learned neither of the attempt nor of its reason.

**After**

The refusal now carries a return address: the page shows a "Back to the app" button, and `redirect_uri` receives `error=access_denied` (`temporarily_unavailable` when Bitrix24 is down), the machine-readable reason in `error_description` and the original `state`. If the registered return address carried a `code` parameter of its own, a refusal strips it — a refusal never arrives mixed with the marks of success. Refusals before the confirmation behave the same way — an expired consent link and a broken CSRF token, including on the "Decline" button.
