# API changes: August 11, 2026

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

### NEW-0811-1: server plans now name their price unit

Every plan in the [GET /v1/infra/providers/{providerId}/plans](/docs/infra/providers/plans) response carries a new `currency` field set to `"Vibes"` — the unit `priceMonthly` and `sleepPriceMonthly` are counted in. Prices used to arrive as bare numbers and the unit had to be read out of the documentation prose; it is now the same machine-readable marker the search cost object in `GET /v1/me` uses.

The field is additive: existing calls keep working unchanged, and neither the numbers nor their meaning moved. The catalog price may still differ from what a given Bitrix24 account is actually billed.

### NEW-0811-2: the application data directory is declared in the deploy body

An application on a dedicated virtual machine starts under an unprivileged account, and the platform handed that account only the extraction directory. A state directory outside it — the very `/opt/data` our documentation recommends for data that must survive a rollout — is created by the administrator-run `install` and `preStart` steps, so it stayed with the administrator and the application's first write there failed with a permission error. The only way around it was handing out permissions by hand in `preStart` on every deploy.

**Before**

The application could only write to its own extraction directory. There was no declarative way to name a state directory.

**After**

Two optional fields were added to the body of [POST /v1/infra/servers/{id}/deploy](/docs/infra/deploy/deploy). `dataDirs` takes up to eight directories outside the extraction directory; the platform creates each one and hands it to the application account on every deploy, and hands it back on a rollback. A path must be absolute, already normalized and inside `/opt`, `/srv` or `/var/lib`; the bare roots are refused with the new `INVALID_DATA_DIRS` code before the deploy takes the server. `dataDirsRecursive` additionally hands over the contents of the declared directories — needed only for a pre-seeded tree, and not accepted for `/opt/data`, because our own database restore recipe keeps a password file there.

What is handed over is the directory itself, not its contents: files the administrator put there earlier do not change owner. Whoever owns a directory can delete and replace the files inside it, so keep scripts you run as administrator and any credentials in a directory you did not declare.

Existing calls behave exactly as before: without these fields no directory is created and no ownership changes.

### NEW-0811-3: issuing a key with exactly the selected platform rights

The `POST /v1/keys` body accepts an optional `exactScopes` field. With `exactScopes: true` the key stores exactly the rights listed in `scopes`: the four platform ones (`vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`) are not appended at issue, and `vibe:ai` / `vibe:search` are not added to the request's rights on the fly. As a result `GET /v1/me` returns exactly the stored set, and a key without `vibe:infra` answers `403 INFRA_SCOPE_REQUIRED` to `POST /v1/infra/servers`.

The default is unchanged: without the field the four platform rights are still added to the requested ones, so scripts already written keep working as before.

Keys issued in the dashboard are now exact as well — clearing a platform-right checkbox means the key does not hold that right. Previously the Vibecode set was appended unconditionally, and the only way to narrow the rights was to edit the key after it had been issued.

### NEW-0811-4: off-peak hours are visible in the Cowork/Code subscription and in the exhausted-quota refusal

During certain hours of the week the quota is consumed more slowly — the same call takes a smaller share of the limit. This used to be visible only in the off-peak schedule, and now the same information arrives in the Cowork/Code subscription responses and in the refusal on an exhausted quota, so an app can offer to move a bulk job into a cheap hour instead of assembling the schedule itself.

| Response | What was added |
|---|---|
| [GET /v1/off-peak](/docs/ai/consumption/off-peak) | `currentWindowEndsInHours` — in how many hours consumption stops being this favourable. `null` when it never gets more expensive within a week ahead |
| [GET /v1/cowork/me](/docs/cowork/me) | The `offPeak` block — whether a discount applies right now, the consumption multiplier and when the next cheap hour arrives. Without the hour grid |
| [GET /v1/cowork/state](/docs/cowork/state) | The same `offPeak` block together with the week-long hour grid, plus the `touSavedPct` field — the share of this billing period's consumption the off-peak hours took off |
| The 402 `cowork_quota_exhausted` refusal on [POST /v1/chat/completions](/docs/ai/chat/completions) | `offPeakHint` — in how many hours the block is lifted (`inHours`) and which consumption multiplier will be in effect at that moment (`multiplier`). It arrives only when that moment falls into a discounted hour |

All of these keys are optional and arrive when the capability is enabled for the Bitrix24 account. It is switched on account by account, and until it is on, the keys are absent from the response body entirely — they never arrive as `null`. Test for the presence of the key, not for its value.

Off-peak hours are not in effect on this platform yet: the schedule reports full price, `currentWindowEndsInHours` arrives as `null`, and neither the subscription block nor the refusal hint arrives at all. The fields are part of the contract, so a client written against them keeps working once the hours are switched on here.

The multiplier is a consumption coefficient, not the discount size. A value of `0.5` means the call takes half the quota share it would take without a discount.

The block fields, response examples and specifics — [Off-peak hours in the Cowork/Code subscription](/docs/cowork/off-peak).

### FIX-0811-5: the next off-peak hour is counted from the hour boundary, not from the minute of the request

**Before**

The `nextWindow.inHours` field of [GET /v1/off-peak](/docs/ai/consumption/off-peak) was counted from the moment of the request. The schedule is hourly, so an answer of "in 2 hours" received at 10:55 pointed at 12:55 — the middle of the cheap hour that had started at 12:00. A client adding that number to the time of its request landed inside the cheap hour with most of it already gone. The same counting behaved the same way in the off-peak card of the dashboard (`GET /api/ai/tou`).

**After**

The count runs from the boundary of the current hour in the schedule's timezone. The same "in 2 hours", received at any minute between 10:00 and 11:00, points at 12:00 — the start of the cheap hour. The field is now what its documentation describes — the nearest hour that is cheaper than the current one. The divergence from the previous reading is at most one hour. The same fix applies in the off-peak card of the dashboard.

**Impact on integrators**

Nothing to change, the response shape and the field type are the same. A scheduler that adds `inHours` to the time of its request now starts at the beginning of the cheap hour instead of its middle, and the start moment can shift by at most one hour. Off-peak hours are not in effect on this platform yet, so `nextWindow` arrives as `null` here until they are switched on.

### BC-0811-6: a server whose operating system does not boot answers with a dedicated code

> Old format supported until: not provided

**Before**

A server whose guest operating system does not start after an interrupted update looked like an
ordinary "temporarily offline" one. Repair was launched again and again and each time returned a
message about the SSH key, which has nothing to do with the failure, while
`POST /v1/infra/servers/:id/start` answered `200` and brought up a machine that would not boot
anyway. There was no way to tell a hopeless machine from a temporarily unreachable one from the API
responses.

**After**

Such a machine carries the machine-readable marker `provisionErrorCode = "GUEST_NOT_BOOTING"` in
[GET /v1/infra/servers/:id](/docs/infra/servers) and in the server list. `POST /v1/infra/servers/:id/repair`
and `POST /v1/infra/servers/:id/start` answer `422` with the code `GUEST_NOT_BOOTING` instead of
doing knowingly useless work, `availableActions` keeps only `delete`, and [GET /v1/me](/docs/quickstart)
advises recreation instead of repair in its `infra.unhealthyServers` block. Billing for such a
machine is closed at the moment of the verdict.

**What integrators should do**

Check `provisionErrorCode` before start and repair: the value `GUEST_NOT_BOOTING` is terminal,
retrying will not help, the server has to be recreated. A client that calls `start` on a schedule
will get `422` on such a machine instead of the former `200` — handle that code as "recreate", not
as a temporary error. The old behaviour is not kept: the `200` meant starting a machine that does
not boot anyway, and kept billing for it.

### NEW-0811-7: the "app is not responding" reply now has a separate code for a server with no deploy

**Before**

When the tunnel to the server was open but the app did not respond, a machine caller always got the
same `503` with the code `BH_APP_STARTING` and a `Retry-After` header. A server that had never been
deployed to was indistinguishable from one where the app had crashed or was listening on the wrong
port, and retrying on a timer looked reasonable where there was nothing to wait for.

**After**

If the platform has no deploy on record for that server, the reply carries the code
`BH_APP_NOT_DEPLOYED` and no `Retry-After` header — retrying on a timer is pointless, the fix is a
deploy or a check of the address. The previous `BH_APP_STARTING` with `Retry-After` stays for the
case where a deploy did happen: there the app really can come up on its own. The status is `503` in
both cases, so handling that does not branch on the code keeps working as before.

### FIX-0811-8: document template creation declares its required scope in openapi.json

**Before**

The [POST /v1/doc-templates](/docs/entities/doc-templates/create) operation carried no `x-required-scope` field in the machine-readable spec, although the runtime answers `403 SCOPE_DENIED` without the `documentgenerator` scope. A client or agent deriving key scopes from the spec read it as "no scope needed" and was refused on the first call. The same operation also appeared in every `openapi.json?scope=` slice, including slices of other modules.

**After**

The operation declares `x-required-scope: documentgenerator`, like the rest of the module. In slices it is now kept only for `openapi.json?scope=documentgenerator` and in the full spec.

**Impact on integrators**

No action required: the endpoint itself behaves exactly as before. Clients that derive their scope set from the spec will now request `documentgenerator` up front and stop hitting the `403`.

### FIX-0811-9: Requisite user fields in responses

**Before**

`crm.requisite.get` did not return requisite user fields in V1 responses.

**After**

After a successful `crm.requisite.get`, the service fetches requisite user fields with a narrow side-read and keeps the successful response if that extra call is unavailable.

### NEW-0811-10: granted_scopes — the issued key's actual scopes in the exchange response

[POST /v1/connect/token](/docs/partner-connect) now returns a new `granted_scopes` field — the scope set the issued API key actually carries. The `scopes` field is unchanged: it still holds what the app requested at authorization. The two sets usually match, but they diverge for apps whose scopes the platform assigns itself — with the Cowork desktop device sign-in, for instance, the app requests nothing, so `scopes` arrives empty while `granted_scopes` lists the key's full set. The field is optional and may be absent if the platform could not read the issued key's rights. It never arrives empty, so a missing field means the set is unknown rather than that there are no rights. Existing calls keep working.

### FIX-0811-11: OpenAPI: required scopes for bespoke operations

**Before**
The machine-readable OpenAPI contract omitted x-required-scope for bespoke operations even though their handlers already checked the scope before calling Bitrix24.

**After**
The contract publishes the handler-enforced scope for CRM, bots, calls, chats, lists, note, notifications, posts, scrum, tasks, timelines, userfields, warehouses, workday, and workflows operations. The download proxy with two accepted scopes remains unannotated because one scalar annotation would be inaccurate.

### FIX-0811-12: the /v1/guide reference returns a docs link for the telephony-lines entity

**Before**

In the `GET /v1/guide` response the `telephony-lines` entity arrived without a `docs` field. The page describing lines existed, but the reference response gave no way to find it.

**After**

The `telephony-lines` entity carries `docs` pointing at the telephony section `/docs/telephony/lines`. No other response field changed and no client action is required.

### FIX-0811-13: a write with Content-Type: text/plain is now rejected instead of creating an empty record

**Before**

A create or update request (POST/PATCH) with `Content-Type: text/plain` and a non-JSON body was accepted: the body was silently dropped and the write still ran — POST created an empty entity with 201, PATCH silently changed nothing. Every other non-JSON type already returned 415.

**After**

Such a request returns `415 Unsupported Media Type`, like other non-JSON types; no empty record is created. Send the body as `application/json`.

### BC-0811-14: deploy no longer reports success when the port stayed with the previous process

> Old format supported until: not provided

**Before**

A deploy on [POST /v1/infra/servers/{id}/deploy](/docs/infra/deploy/deploy) could answer `success: true` with `healthcheck: ok` while the application port was still held by a process that had been there before the deploy started. The health check got its `200` from that process rather than from the new application, so the public address kept serving the previous version. The `stop_existing` step did warn that the port owner was unrelated to the new application, yet the deploy went on and finished as a success.

**After**

When the health check gets a `200` while the port is held by a process that was already on it before this deploy began, the `healthcheck` step fails. The error text names the port owner. The response becomes `success: false` with `status: "error"` on that step, and in streaming mode the stream stops at the same step.

Deploys where the port is published by a process this very deploy started still finish successfully — including one brought up by the `install` or `preStart` step, that is, before the service itself starts. The case where the port owner cannot be determined is unchanged as well: such a deploy still counts as a success.

**What integrators should do**

Check whether the application port is held by a process that outlives deploys: a reverse proxy, a process manager, or a container or systemd unit brought up once from the `install` or `preStart` step and not recreated on later deploys. That setup used to answer `success: true` and will now answer `success: false`: the platform cannot confirm that the new version is the one answering at that address. Give the application port to the application and move your own process to a different port. Everyone else has nothing to change: the response shape is the same, and a flow that used to receive `success: true` while the new version never came up now receives `success: false` with the reason.

### NEW-0811-15: Cowork/Code tier change cost preview

`GET /v1/cowork/subscription/preview?tier=<FREE|PRO|MAX|ULTRA>` returns the amount that will be debited right now if the caller switches to the given tier: the full price of the operation, the credit for the unused part of the paid month, and the resulting net. Scope `vibe:cowork`, the response is never cached, and the endpoint is limited to 30 requests per minute per account and user.

Build the confirmation screen on this response rather than on the tier price in `tiers[].feeVibes` from `GET /v1/cowork/state`: that price is a sticker, and the debited amount already differs from it in four cases. Choosing the tier the seat already holds debits nothing; a downgrade queued for the end of the paid period debits nothing now; asking again about such a queued downgrade also debits nothing; and an upgrade credited for the unused month debits the price minus the credit. The pair `netVibes: "0"` and `scheduled: true` answers "will this charge me now" outright, so those rules need not be reimplemented in the client.

Vibe credits arrive as a decimal string in major units, matching the wallet balance and account movements. Alongside them the response carries `currency` (an ISO 4217 top-up currency code or `null`) and `topUpAvailable`; both describe topping up the wallet, not the price of the tier — the response carries no money price for a tier and no Vibe-to-money rate.

### FIX-0811-16: app install failure message now names the Bitrix24 answer

**Before**

When Bitrix24 refused a developer-key app install and the cause matched no known
code, the response carried `DEVKEY_MINT_FAILED` and the generic text `Failed to
install app via developer key`. It gave no way to tell an access refusal from an
unavailable REST module or a transport failure.

**After**

The same text now carries what we observed: `Failed to install app via developer
key (Bitrix24 answered HTTP 403 BITRIX_REST_V3_EXCEPTION_ACCESSDENIEDEXCEPTION)`.
The error code (`error.code`), the HTTP status and the `userMessage` field are
unchanged, so code-based handling keeps working as is. Refusals with a recognised
cause — subscription, plan, stale key — keep their previous text. The same
wording reaches the dashboard: the warning about an auth key that was not issued now
names the Bitrix24 answer.

The answer is appended only when there was one: on a transport failure, where
Bitrix24 never replied, the text stays as before. The refusal code is appended in
machine form and never longer than 64 characters.

### FIX-0811-17: read-only keys no longer reject read operations as writes

**Before**

A read-only key could receive `403 WRITE_BLOCKED_READONLY_KEY` for requests that read data through the Bitrix24 methods `timeman.status`, `timeman.settings`, `calendar.event.getbyid`, `bizproc.workflow.instances`, `lists.get.iblock.type.id`, `lists.element.get.file.url`, `crm.type.getByEntityTypeId`, and `crm.activity.call.getTranscript`.

**After**

A read-only key allows these read operations. Unknown methods are still treated as writes and blocked.

### BC-0811-18: marking messages read now requires a key with write access

> Old format supported until: not provided

**Before**

A key in [read-only](/docs/keys-auth) mode could mark notifications and messages as read: [POST /v1/notifications/read](/docs/notifications/read), [POST /v1/chats/:dialogId/read](/docs/chats/messages/read) and [POST /v1/bots/:botId/chats/:dialogId/read](/docs/bots/messages/read) went through and changed account state — the unread counter, notification statuses. They passed because write access is decided from the name of the Bitrix24 method being called, and these names end in the word "read", so they were taken for reads.

**After**

Under a read-only key all three operations answer `403 WRITE_BLOCKED_READONLY_KEY`. The code is now listed in the error reference of each of the three pages. A key with write access works as before.

**What integrators should do**

If your scenario marks things read, issue or switch a key to read-write mode in the keys section of the dashboard. Reading notifications and messages with a read-only key is unchanged. Chat event subscription (`POST /v1/chats/events/subscribe` and `POST /v1/chats/events/unsubscribe`) is still available to a read-only key — a deliberate exception, without which a read-mode agent could not follow events.

### FIX-0811-19: five more read operations stopped being rejected under a read-only key

**Before**

Entry FIX-0811-17 lifted the rejection for eight read methods, but five operations of the same class remained: `GET /v1/tasks/:taskId/chat/messages`, `GET /v1/humanresources/nodes/:id/children`, `GET /v1/humanresources/employees/:id/subordinates`, `GET /v1/mail/messages/:id/thread` and `GET /v1/mail/mailboxes/:id/senders`. A key in [read-only](/docs/keys-auth) mode answered them with `403 WRITE_BLOCKED_READONLY_KEY`: write access is decided from the name of the Bitrix24 method being called, and these operations end in a generic word, so an unrecognised name was treated as a write. No one had reported any of them — the mismatch surfaced from a sweep of every method against its route.

**After**

All five answer a read-only key like any other read. The `403 WRITE_BLOCKED_READONLY_KEY` rejection stays on write operations in the same sections: sending an e-mail, editing the org structure, posting to a task chat.

**Impact on integrators**

Nothing to change: requests that used to be rejected now go through. If you issued a read-write key just for these operations, switch it back to read-only.

### NEW-0811-20: Cowork/Code state tells the account how to open up work with Bitrix24

The [GET /v1/cowork/state](/docs/cowork/state) response now carries an `activation` block. It names the access model of the account region (`model`: `subscription` or `tariff`), links to the Bitrix24 plan terms (`tariffInfoUrl` — only when `model` is `tariff` and the account is not self-hosted) and answers up front whether the Marketplace trial is worth offering: `marketTrial.available` and `marketTrial.unavailableReason` (`trial_already_activated`, `subscription_active`, `demo_used`, `region_not_supported`, `not_cloud`, `portal_state`, `not_supported`), plus the journal of our own attempts — `status`, `endsAt`, `activatedAt`.

International accounts run on the tariff model, so they get `model: tariff` with the plan terms link, and the trial is reported as unavailable with `region_not_supported`. The flag is computed before any attempt and `false` is final; `true` means offering is fine but does not promise success, so keep handling a refusal at activation time. The list of reasons may grow — read an unknown value as "do not offer the trial". The `activation` block itself may be absent from the response: that is how a platform that does not know about it yet answers, and it is a normal state — test for the block, then read the value of the field inside it.

### FIX-0811-21: Cowork/Code state now has a polling ceiling

**Before**

[GET /v1/cowork/state](/docs/cowork/state) accepted requests at any rate, even though the documentation recommends polling it once every 15–30 seconds.

**After**

There is a ceiling now, shared by the account and the key owner, so every device of one person draws on the same budget. Above it the endpoint answers `429 RATE_LIMITED`.

**Impact on integrators**

A client that keeps the recommended interval will not notice the ceiling — it spends about two requests per minute, several times below the limit. If your polling is faster, space it out or handle the `429`.

### FIX-0811-22: the upgrade link in the capabilities response no longer leads nowhere

**Before**

In [GET /v1/me](/docs/keys-auth/me), when server creation was refused, the `capabilities.servers.create.alternatives[].url` field (and the same address inside the `userMessage` text) pointed at the licence page inside the account itself. That address differs between Bitrix24 editions and versions and answered 404 on some accounts — the platform moved off it in every other response back in April, and only this one was left behind.

**After**

A stable address arrives instead: the account checkout page where access is opened by a subscription, and the shared Bitrix24 plan terms page where access is opened by a commercial plan. The "subscribe" wording is no longer shown to regions that have no subscription as a product — they get the general wording plus the plan terms link.

**Impact on integrators**

No action required: the field is still there and may still be absent. Do not persist the value and do not parse its host — it is a platform address, not an address inside your account.

### FIX-0811-23: the ai_congested retry pause now scales with the configured base and is capped

**Before**

When the platform throttled the flow of AI requests, [POST /v1/chat/completions](/docs/ai/chat/completions),
[POST /v1/embeddings](/docs/ai/embeddings) and [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions)
answered `429` with the `ai_congested` code, a `Retry-After` header and a `retryAfter` body field. The pause
barely varied — consecutive refusals came back with near-identical values — and it had no upper bound.

**After**

The pause varies more widely and may come back **longer** than it used to; it is now capped — never more
than an hour. How much longer depends on how the platform is throttling at that moment, so the only correct
behaviour is the one it always was: wait exactly as long as the response says.

The `Retry-After` header and the `retryAfter` body field still carry the same number.

**Impact on integrators**

No action needed: the response shape, the `ai_congested` code and the set of fields are unchanged. If your
client assumed the pause fits within the base plus two seconds, drop that assumption and wait for as long as
`Retry-After` says. The same applies to calls through the deprecated `/v1/ai/*` addresses, which reach the
same handlers.

### NEW-0811-24: an interrupted galaxy deploy now names its cause and flags repeats

The `502 GALAXY_DEPLOY_INTERRUPTED` response now carries two new optional fields. Existing clients keep working unchanged: the error code, `retryable` and `error.hint` are all still there.

`error.subcause` names what actually happened — until now every interrupt looked the same and the only advice was "retry":

- `http_window_exhausted` — the request window ran out before the platform checked even once. The build may well have finished on the host, and the next attempt confirms it in seconds.
- `exec_channel_busy` — the host command channel stayed busy until the wait budget ran out.
- `no_this_deploy_container` — the host answered, but no healthy container of this very deploy was found.
- `tail_unreached` — the connection dropped and no clean answer arrived before the deadline.
- `source_fetch_interrupted` — the source archive fetch was interrupted before the build started, and nothing of the app was touched.

`error.repeated` turns `true` once the same app has been interrupted several times inside a short window. `error.hint` then stops advising a plain retry and asks you to check whether the app starts at all — and, if the app is known-good, to contact support with the server id.

The error text also stopped claiming the host is reachable in the case where the platform never asked it.

Affected endpoints: [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy)

### FIX-0811-25: galaxy app creation now reports that the provider, plan and region you sent were not applied

A galaxy app is a container on a shared host: it has no machine of its own, so it inherits the provider, plan and region from that host. This was always the case, but the response said nothing about it — the values you sent were replaced by the host's without any signal, and a typo in a plan identifier looked like an accepted request.

**Before**

`POST /v1/infra/servers` on an account that places apps in galaxies answered `201` and returned the host's values in `data.provider`, `data.plan` and `data.region`. Nothing in the response distinguished "the platform used mine" from "the platform used something else".

**After**

The same request still answers `201` and still inherits the host's values — the behaviour is unchanged. The response changed: a `warnings[]` entry now sits next to `data` whenever a value you sent differs from the one in force. It names the diverging fields, shows both values, warns that re-sending will change nothing, and points at `placement` set to `dedicated` — the way to get a machine whose characteristics you choose. Fields that matched are not mentioned, so a correct call stays free of warnings. A value that is over-long or not shaped like a catalog identifier is described in words rather than echoed back.

### BC-0811-26: uploading a file to a galaxy app is refused instead of writing to the shared server

> Old format supported until: 01.02.2027

**Before**

[POST /v1/infra/servers/:id/upload](/docs/infra/deploy/upload) accepted the id of a galaxy app (`kind: GALAXY_APP`) and wrote the file — not into the app container, but onto the filesystem of the shared server that hosts the account's other apps. The response was `200`, so the miss looked like success.

**After**

The same call answers `400 GALAXY_APP_USE_GALAXY_ROUTE` — the code `exec`, `logs` and `deploy` already return for galaxy apps. The message names the cause: the write went to the shared server, not into the container.

**What integrators should do**

Ship files into a galaxy app by rebuilding its image from sources — `source` in the body of [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy). Uploading to a standalone server (`kind: STANDALONE`) is unchanged.

### FIX-0811-27: an app from the Bitrix24 catalog now opens for an employee with no Vibecode account

**Before**

An app opened from the Bitrix24 catalog started only for employees who already had a personal Vibecode account. Everyone else got a "sign in to VibeCode and connect this account" screen — even when the app had been granted to the whole Bitrix24 account. So an administrator, who had an account, saw a working app while regular employees did not.

**After**

A Vibecode account is no longer a condition of access. An app with the whole-account policy (as well as any-authenticated and public) opens for every employee: membership is vouched for by the signed form, and the platform additionally asks Bitrix24 that this is an active employee, not a fired one and not an external guest. Identity-bearing policies (owner only, named users, departments) still do not open through this path — they decide on a specific person, so the sign-in screen stays there.

What to keep in mind when building an app: for an employee with no account the `X-Vibe-User-Role` header always arrives as `MEMBER`, even when they are an administrator, and `X-Vibe-User-Name` arrives as `Unknown`. Do not gate irreversible decisions on those headers; check rights by calling Bitrix24.

### BC-0811-28: AI requests now carry a service deadline: a 429 refusal instead of a hang

> Old format supported until: not provided

**Before**

A call to [POST /v1/chat/completions](/docs/ai/chat/completions), [POST /v1/embeddings](/docs/ai/embeddings) or their `/v1/ai/*` aliases had no declared upper bound. A regular response ran into an internal wait limit and arrived as a dropped connection with no body, and a streamed response had no overall time limit at all: once the cluster stopped sending chunks, the connection hung until the client's own timeout. There was nothing to tell "still working" from "will never answer".

**After**

A request now has a service deadline. When it is exceeded you get `429` with the body `{"error":{"code":"ai_deadline_exceeded","type":"rate_limit_exceeded","retryAfter":<seconds>}}` plus the `Retry-After` and `X-AI-Deadline-Ms` headers (the request's actual budget in milliseconds). On a streamed response, where the status has already been sent, the same body arrives as a stream event followed by `data: [DONE]`.

A client may ask for a shorter budget with the `X-AI-Deadline-Ms` request header, in milliseconds. It only shortens the deadline: nothing longer than the platform value is granted, and where the deadline is switched off the header does not switch it on. A non-numeric or non-positive value counts as absent and never rejects the call.

The deadline also covers the retry on a fallback model: it is measured from the moment the request arrived, not from the start of the attempt.

**What integrators should do**

Treat `429` with code `ai_deadline_exceeded` as an invitation to retry: wait the number of seconds in `Retry-After` and send the request again. For streamed calls, add handling for an event carrying an `error` field — the stream did not emit one for this reason before. If your client has its own wait limit, pass it in `X-AI-Deadline-Ms`: then the refusal comes from us with an explanation instead of timing out on your side.

### FIX-0811-29: the server region catalog on the international version no longer returns zones from unavailable regions

**Before**

`GET /v1/infra/providers/{providerId}/regions` on the international version of the platform returned region zones that are not available for provisioning in that segment — they were listed alongside the available ones.

**After**

The list is filtered by segment: on the international version the response keeps only the zones available for provisioning in that segment. No client change is required — the response is simply correct now. On the primary version of the platform the list is unchanged.
