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

API changes: August 20, 2026

← Changelog · August 2026

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 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, 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, 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 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 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 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 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.

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.

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.

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.