For AI agents: markdown of this page — /docs-content-en/changelog/2026-09-02.md documentation index — /llms.txt

API changes: September 2, 2026

← Changelog · September 2026

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, the Vibecode API passed their values without normalization. The same fields in POST /v1/documents and PATCH /v1/documents/:id 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 is now available — it returns the booking resources available to the key and closes the gap in the first-booking flow: POST /v1/bookings 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-5: money-in export marks revoked orders and returns the recognizable amount

In the per-payment export GET /v1/platform/revenue/money-in/payments every row now carries a revokedAt field — the package revocation date in UTC ISO-8601 format, or null for a live order. A revoked order's row does not disappear from the export and its amounts do not change: the money did arrive on the paymentDate, and cash-in still reconciles with the bank statement.

In the aggregate GET /v1/platform/revenue/money-in two nested tiers now travel alongside the previous sums. The revoked tier is the revoked part of the same values (ordersCount, grossCashRub, vatRub, netRubExclVat, vibesCredited), and the recognized tier is the same values minus the revoked part, that is the recognizable amount. Top-level fields do not change their values and remain the cash-in for the period, so existing integrations keep working with no changes.

A revocation arrives as a separate event and may happen after the period is closed, so on a repeated export a previously exported row may turn out to have revokedAt filled in. The set of rows stays the same, and the orderId key is stable.

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/Code 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 off by default and is switched on by a platform administrator; 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 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: the Performance Review section is available through /v1/performan/review

Six endpoints are now available under the new performan scope. Reads: GET /v1/performan/review/campaigns, GET /v1/performan/review/self-reviews, GET /v1/performan/review/peer-reviews, GET /v1/performan/review/manager/questions, GET /v1/performan/review/manager/reviews. Every list but campaigns requires the campaignId parameter; manager cards also accept an optional revieweeUserId filter. The response is { "success": true, "data": [...], "meta": { "nextCursor": ... } }. Pagination is cursor based: limit (1..200, 50 by default) and afterCursorId, taken from meta.nextCursor.id of the previous page. A limit outside that range answers 400. These methods have no offset parameter and no total-count field.

Every list is scoped to the calling user: their campaigns, their cards and the manager relations where they are the reviewer. An empty response means "nothing for this user", not "nothing on the account".

Write: POST /v1/performan/review/manager/answers with a body of { "relationId": 4, "answers": [...], "isAutosave": true, "expectedStateHash": "..." }. The isAutosave field defaults to true, which saves a draft; pass false to finalise the review, which moves the card to the completed status and cannot be undone. The expectedStateHash value is returned only by a write, never by a read method, so the first call goes without it. When the state changed since it was read, the answer is 409 with the PERFORMAN_STATE_CONFLICT code: re-read the card and repeat the request with the fresh value. A relation that belongs to somebody else and a relation that does not exist both answer 403. A read-only key gets 403 WRITE_BLOCKED_READONLY_KEY on the write.

The section is only available on accounts where the Performance Review module is installed and the surface is enabled for them: elsewhere all six addresses answer 404 and are absent from that account's GET /v1/openapi.json. The performan scope is withheld from the default set for the same reason.

A Cowork key gets the scope reactively: if it has not been granted yet, the first request to any of the addresses answers 409 PERFORMAN_SCOPE_JUST_GRANTED and grants the right on the spot — retry the same request and it will succeed. A read-only key does not get the right this way: the reactive grant is a write, so such a key is refused with 403 and its owner adds the scope in the dashboard instead.

A choice question cannot be answered through the API: no read method exposes option ids, and the question list returns a question without its options.

GET /v1/openapi.json 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 depends on the request's key: with a key of an account the performan surface is enabled for, it carries the performan paths and the performan scope in the catalogue; without a key it does not. The response is therefore marked Vary: Authorization, X-Api-Key (the key is read from either header), and with such a key 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 → 502 CONNECTOR_KEY_ISSUE_FAILED) and where an app is installed (POST /v1/apps → 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.

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.