# API changes: August 20, 2026

[← Changelog](/docs/changelog) · [August 2026](/docs/changelog/2026-08)

### NEW-0820-1: the Cowork subscription snapshot is described in the API specification

**Before**

`GET /v1/cowork/me` worked and was listed in the `/v1/guide` directory, but the V1 specification did not carry it. You could not generate a client from the spec or verify the response shape — reading the directory by eye was the only option.

**After**

The operation is described: tier and subscription state, the three quota windows as integer percentages with their reset time, and the next charge date. It also states that the successful body is the object itself, with no `success` wrapper, while errors arrive in the usual `{ success: false, error: { code, message } }` envelope. The `offPeak` and `relief` blocks are described as absent when the feature is disabled: check for the presence of the key rather than comparing the value with `null`.

### FIX-0820-2: `include` examples use relation names

**Before**

OpenAPI and MCP showed plural related-entity names for the `include` parameter, causing requests to fail with `INVALID_INCLUDE`.

**After**

Examples use the actual relation names `contact,company`.

### FIX-0820-3: MCP now forwards timeline log action parameters correctly

**Before**

The `manage_timeline_log` tool accepted only string `id` values, omitted the request body for `pin` and `unpin`, and omitted query parameters for `get_note` and `delete_note`.

**After**

The tool accepts string or numeric `id` values and forwards the required parameters for these actions to `/v1/timeline-logs/*`. The delete description now states that personal keys cannot delete these entries: deletion is available only to the same OAuth application that created the entry.

### NEW-0820-4: calendar sections now expose schema and search routes

**Before**

`calendar-sections` had no `GET /v1/calendar-sections/fields` or `POST /v1/calendar-sections/search`, so agents could not discover fields and valid calendar types in advance or use the standard search surface.

**After**

`GET /v1/calendar-sections/fields` returns the static schema with required fields and the `user`, `group`, `company_calendar`, and `location` types. `POST /v1/calendar-sections/search` accepts owner context through `filter.type` and `filter.ownerId`; additional filters are explicitly rejected because the Bitrix24 method does not support them.

### FIX-0820-5: personal keys report only executable scopes

**Before**

[GET /v1/me](/docs/management-keys) could report the `placement`, `entity`, and `userfieldtype` scopes for a personal key even though these features require an OAuth application context. Key creation and update also accepted `userfieldtype` as a regular scope.

**After**

[GET /v1/me](/docs/management-keys), personal-key creation, and personal-key update exclude `placement`, `entity`, and `userfieldtype`. The `userfieldconfig` scope remains available. OAuth application keys are unchanged.

**Impact on integrations**

No action is required. Use an OAuth application key for placements and custom user field types.

### FIX-0820-6: the tasks permission now works on every task operation, no matter how the key was issued

**Before**

A key holding the tasks permission was refused on some task operations — which ones depended on how Bitrix24 had issued the key's webhook. Bitrix24 accepts two spellings of the same tasks permission (`task` and `tasks`), and they open different groups of operations: older task operations require the first spelling, newer ones the second. A key kept the spelling its owner had picked, and on some issuing paths only that one spelling reached Bitrix24. Inside `POST /v1/batch` such a refusal arrived within a `200` response, on the sub-command rather than the whole request.

**After**

The tasks permission is registered in Bitrix24 in both spellings regardless of the issuing path, so all task operations are available. Keys issued earlier reach the same state when their webhook is reconnected. The key's own permission list and the `GET /v1/me` response are unchanged — no client action is required.

### FIX-0820-7: bot chat creation returns the identifier the other actions accept

**Before**

After creating a chat, the `manage_bot_chat` tool put a numeric identifier in the most visible places — `data.chat.id` and `data.recentConfig.chatId`. Reusing that number in the next action — [GET /v1/bots/:botId/chats/:dialogId](/docs/bots/chats/get), leaving the chat, transferring ownership — returned a `BITRIX_ERROR` "chat does not exist": for Bitrix24 a bare number in `dialogId` means the personal dialog with the user of that id, not the group chat with that id. The usable identifier, such as `chat471`, sat deeper in `data.chat.dialogId`, and the tool description did not name that format.

**After**

The chat creation response starts with a `chatId` field holding a value such as `chat471`, and that is the value the other actions of the tool accept. The Bitrix24 fields `data.chat.id` and `data.recentConfig.chatId` are unchanged and stay numeric. The description of the `chatId` parameter now names both formats: `chatN` for a group chat, a bare number for the personal dialog with that user.

**Impact on integrations**

MCP callers need to do nothing. The [POST /v1/bots/:botId/chats](/docs/bots/chats/create) response is unchanged for REST: there you still have to take `chat.dialogId` rather than the numeric `chat.id`. [GET /v1/bots/:botId/chats/:dialogId](/docs/bots/chats/get) and the other actions still accept both `chatN` and a number as a personal dialog identifier.

### FIX-0820-8: AI provider refusal: a readable message instead of relayed prose, and one error shape for streaming and non-streaming

**Before**

When the AI provider refused the platform's request, `POST /v1/chat/completions`,
`POST /v1/embeddings` and `POST /v1/audio/transcriptions` answered `502 ai_provider_unavailable`
and put the provider's own wording into `error.message` verbatim. The provider could report a
failure of its own infrastructure as an authorization error — so the response spoke about
authorization where neither the caller's API key nor the caller's network was involved, and the
caller went auditing both.

Streaming (`stream: true`) returned the same failure in a different shape than a plain request:
`code` arrived in upper case instead of lower case, there was no `type` field at all, and
`retryAfter` and `retryable` were reserved for a stalled stream and for overload. A client
branching on `error.type` or on `retryable` could not tell a temporary refusal from a final one,
and had nothing to wait on.

**After**

The wording now depends on who owns the credential the provider refused. A key the caller
connected themselves (BYOK) — the provider's message passes through as before: it is addressed to
the key owner and tells them what to do. An account-level credential — the response names the
object and the person who can update it, and states plainly that retrying will not help. A
platform credential — the response says the caller's API key and network are not the cause. The
`502` status, the `ai_provider_unavailable` code and the `providerStatusCode` field are unchanged
in every case.

The streaming error took the same shape as the non-streaming one: `code` in lower case, `type`
present, and every frame carrying a `retryAfter` pause now carries `retryable: true` as well. Both
fields appeared for a temporarily unavailable provider, for rate limiting and for the cooldown
after a run of failing calls — previously only a stalled stream had them.

The retry hints now agree with the wording of the response: a refusal a retry cannot fix carries
neither the pause nor the flag. That covers a refusal of the request body itself
(`ai_provider_rejected`) and a provider-refused account credential or the caller's own key, where
the response states outright that the credential has to be updated. A temporarily unavailable
provider stays retryable.

### FIX-0820-9: an attachment whose extension does not match its content is no longer rejected

**Before**

`POST /v1/feedback/attachments` compared the type declared by the client with the file's own signature and answered `400 MIME_MISMATCH` when they differed. The client derives that type from the extension, so a JPEG saved as `image.png` arrived as `image/png` and was rejected — even though both formats are allowed and the file was intact.

**After**

Processing is driven by the file's actual format. A mismatch between two allowed formats (PNG, JPEG, WebP, GIF) is accepted, the file is re-encoded from its content, and its `mime` in the response is the result of that re-encode, as before. `MIME_MISMATCH` is left for the single case where the file's signature is not recognized at all.

### FIX-0820-10: MCP now preserves one chat identifier format

**Before**

The `manage_chat.add_users` action passed a value such as `chat457` to a numeric backend route without normalization, so Bitrix24 reported an empty chat ID. A chat created through the tool could not be left through the same MCP tool, and `find` looked like text search.

**After**

`add_users` and the new `leave` action accept the standard `chatN` `dialogId` value and pass a numeric ID to the route. `add_users` preserves its legacy positive numeric chat ID for compatibility, while the new irreversible `leave` action requires an unambiguous `chatN`; the caller must convert a numeric ID returned by chat creation to `chatN` before calling `leave`. A missing or invalid ID is rejected before any network call. The tool warns that an owner must transfer chat ownership first using the numeric ID without the `chat` prefix. The `find` description now names the required `entityType` and `entityId` parameters for a CRM-linked chat lookup.

### FIX-0820-11: addresses now apply select — records narrow, and an unknown name is no longer lost

**Before**

The `select` parameter is documented for every entity, but on
[addresses](/docs/entities/addresses/list) it did nothing at all. List, search and get-by-composite-key
answered with the full record no matter how many names the caller listed, and an unknown name
disappeared without a trace — no error, no warning. This was the only entity where field selection
was completely silent.

**After**

All three address doors apply `select` the way every other entity does: only the listed fields stay
in the records, canonical names and native Bitrix24 names are both accepted (`CITY` projects `city`),
and `*` still means "return every field".

An unknown name behaves differently from door to door. Get-by-composite-key picks the fields on the
Vibecode side, so a name absent from the schema arrives as an `UNKNOWN_SELECT_FIELD` warning in
`meta.warnings` while the record itself comes back — that door gains a `meta` block only when there
is something to warn about. List and search pass the listed names on to Bitrix24, so the account
decides the outcome there: one that has no such field rejects the whole call — the answer is
`422 BITRIX_ERROR`, the name is quoted in the message, and no data arrives.

The composite address key — `typeId`, `entityTypeId`, `entityId` — always comes back, even when the
`select` does not list it. On most entities a single `id` field plays that role: it is what tells one
record from its neighbour and what the update and delete addresses are built from. On addresses all
three fields carry that role together.

Separately: the name `id` in `select` follows the same split. On get-by-composite-key it is no longer
treated as a typo — addresses have no `id` of their own, so `select=id,city` used to return a warning
about the `id` field, and that request now reads as "tell me which record this is" and returns the
composite key. On list and search the name `id` goes to Bitrix24 and is subject to that same
account check.

**Field selection stopped stripping the route key on smart processes and telephony lines**

On most entities the operation address is built from `id`, and `select=id,…` worked as expected. But
there are two where the route key is named differently, and field selection threw it away: on
[smart processes](/docs/entities/smart-processes/list) it is `entityTypeId`, on telephony lines
(`/v1/telephony-lines`) it is `number`. Both fields now stay in the response even when the `select`
does not list them: `GET /v1/telephony-lines?select=name` returns `{name, number}` instead of `{name}`.

On smart processes this removes a trap: the record also carries an internal `id` field that takes no
part in operation addresses — field selection used to keep exactly that one, and an address built from
it led to a different record.

On every other entity the rule is unchanged and worth remembering: **if you list fields in `select`,
list `id` too** — the Bitrix24 methods that honour the selection (deals, contacts, companies, leads,
quotes, smart process items) return exactly what was asked for, and an unrequested `id` will not be
in the response.

**Impact on integrators**

Nothing to change if you never passed `select` to addresses — the response is the same as before.
A call that passed `select` and relied on getting the full record back will now receive only the
requested fields: that is the documented behaviour of the parameter, brought in line with every other
entity. Check field names against
[GET /v1/addresses/fields](/docs/entities/addresses/fields).

### BC-0820-12: a caller-supplied bot token is validated for length and alphabet

> Old format supported until: not provided

**Before**

`PATCH /v1/bots/:botId` accepted any `fields.botToken` value — say `support-bot-2026`. The call answered `200` and Bitrix24 took the new token. There were no length or shape checks: Bitrix24 applies its 40-char cap when a bot is registered and when it is switched to webhook mode, but not on a plain update.

**After**

A caller-supplied token is validated before the Bitrix24 call: 32 to 40 chars from the `[A-Za-z0-9_-]` alphabet. Any value present in the request that does not fit that bound — too short, empty, carrying stray characters, or not a string at all — is rejected with `400` and code `BOT_TOKEN_INVALID`; Bitrix24 is not called and the bot token stays as it was. The bound is the same one used at bot registration, and its lower edge matches the length of the token the platform issues itself.

The reason is that this very token authenticates the events delivered to the bot at `POST /api/bot/webhook`: a short or guessable token would let anyone forge a bot event without any authentication.

**What integrators should do**

If you supply the token yourself, use a random value of 32 chars or more (32 hex chars, for instance) from the `[A-Za-z0-9_-]` alphabet. If you do not supply one, there is nothing to do: the platform issues the token and it passes the bound. There is deliberately no support window for the old behaviour — a weak token already left the bot inoperable, because the platform never stored such a value on its side.

### FIX-0820-13: bot events delivered by webhook without a Bitrix24 address are no longer lost

**Before**

The `POST /api/bot/webhook` receiver identified a bot by the pair "Bitrix24 account + bot number": the bot number is a per-account sequence rather than a global identifier, so the account had to be resolved from `auth.domain` or `auth.member_id` in the request body. For a bot registered through an incoming webhook neither field is reliable: the account address does not arrive in every envelope, and `member_id` only resolves for accounts whose id the platform already knows. An event without an account address was rejected with `403 AUTH_FAILED`, and imbot webhooks are never re-sent: the user's message was lost for good, and from the platform side it looked as if nobody had written to the bot at all.

**After**

The bot is identified by the top-level `auth.application_token` — for a webhook-registered bot that value points at one specific bot on its own, so the account is no longer needed for it. The former domain-based path is kept and behaves as before: it serves registrations whose token arrives in a different shape. Token verification is not weakened — the comparison stays constant-time, and an event carrying another bot's number in the body is still rejected.

The receiver's rejections also got their own codes — `BOT_WEBHOOK_AUTH_FAILED`, `BOT_WEBHOOK_ID_MISMATCH`, `BOT_WEBHOOK_INVALID_BOT_ID`, `BOT_WEBHOOK_BOT_DISABLED` — so lost events show up in the rejection statistics instead of only in the logs.

**One more change in `PATCH /v1/bots/:botId`**

A token supplied by the caller in `fields.botToken` is now stored on the platform side as well. Previously it only reached Bitrix24 while the platform kept the old value, leaving the bot silently inoperable in both directions: outgoing calls got `401` and inbound events were rejected.

**Impact on integrations**

No action required. The receiver's HTTP status codes are unchanged (`403` / `400` / `410`), and the `error` field in the body stays as it was, with a `code` field added next to it. Bots whose events used to be rejected start receiving them without re-registration and without a token change.

### FIX-0820-14: the account receives Bitrix24 scopes only, and a platform-only key no longer gains a webhook on rotate

**Before**

When a webhook was issued, the account received not just Bitrix24 scopes but Vibe platform scopes as well — `vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`. Bitrix24 does not know such scopes and silently ignored them, so the key's real permissions were unaffected, but the set on the wire differed per issuing path: the dashboard sent one thing, `POST /v1/keys` another, the self-hosted channel a third.

The same divergence had a visible effect on secret rotation. A key whose scope set holds platform scopes only gets no webhook in the account — there is nothing to register. Yet rotating such a key sent the account a set of platform scopes alone and got a webhook back that the key did not have before: `b24Ready` flipped from `false` to `true` even though the key still could not call Bitrix24.

**After**

The account receives Bitrix24's own scopes only, identically on every issuing channel. When no Bitrix24 scope remains, no webhook is requested at all: the key stays platform-only and `b24Ready` stays `false` on create and on rotate alike.

**Impact on integrations**

The response shape, the error codes and the key's stored scope set are unchanged: `vibe:*` still appear in `scopes` and still open the platform sections `/v1/ai`, `/v1/search`, `/v1/storage`, `/v1/infra`. No action is required. The only visible difference is for anyone who rotated a key holding no Bitrix24 scope and expected `b24Ready: true` — such a key now honestly answers `false`, exactly as it does on creation.

### FIX-0820-15: on the international segment the tariff refusal names a Vibe+ plan

**Before**

An account on a free Bitrix24 plan received the `INT_TARIFF_REQUIRED` refusal whose
`userMessage` named a paid Bitrix24 plan as the access condition. The pricing page,
meanwhile, states that full access to the Vibecode platform is unlocked by a plan of the
Vibe+ line — the customer read two different conditions inside one product.

**After**

On the international segment the `userMessage` of this code names a Vibe+ plan, the same
condition the pricing page sells. Every surface of the code is covered: the refusal body on
infrastructure creation and wake, the `capabilities.servers.create` slot in `GET /v1/me`,
the gateway interstitial and the key-issuance toast.

The machine field `details.requiredTariffs` is unchanged: it still lists the tariffs that
clear the refusal. Build the purchase advice from that field and use the human-readable
string to explain the reason. The refusal code, its HTTP status and the envelope shape are
unchanged.

Kazakhstan and Uzbekistan accounts, self-hosted accounts and the Russian segment keep the
previous copy — the Vibe+ line is not sold there.

### NEW-0820-16: assigned promo code — the code works only for its recipient

**Before**

A promo code was redeemed by any employee of the Bitrix24 account who happened to have it. A
workshop batch was handed out by name, but the platform did not know the
recipient: a forwarded code worked for whoever entered it first.

**Now**

A code can be issued to a specific email. Such a code is checked against the
Vibecode account email: on a mismatch `POST /v1/cowork/coupon/redeem` answers
`409` with `COUPON_NOT_ASSIGNED_TO_YOU`, and the code itself stays unspent and
still available to its recipient. The `POST /v1/cowork/coupon/preview` check returns
the same code in the `reason` field.

Codes without a recipient behave as before — any employee of the Bitrix24 account can redeem them.

### NEW-0820-17: blocking a leaked key now reports whether its access links were revoked

`POST /v1/platform/keys/revoke-leaked` now revokes the access links the key issued along with the key itself, and the response carries a new `tokensRevoked` field. The key is always blocked; `tokensRevoked: false` means the key itself is already dead while its links are still alive — the revocation did not go through because of a transient database failure. Repeat the same call: it is idempotent and finishes the job.

### FIX-0820-18: revoking a key now closes the access links it issued

**Before**

`PATCH /v1/keys/:id` with status `REVOKED` disabled the key itself but left the access tokens it had issued untouched: share links and bearer tokens kept working until their own expiry, which reaches ten years. An owner revoked a key and believed access was closed, while the links stayed alive.

**After**

Revoking a key now revokes the access tokens it issued and drops them at the gateway. This matches key deletion, where it always worked that way, and behaves identically on both surfaces — through the API and through the dashboard. Links issued by keys revoked BEFORE this release are closed too: a one-off data fix retires them, not just the new behaviour. Other key changes (name, rate limit, access mode) still leave tokens alone.
