# API changes: August 31, 2026

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

### FIX-0831-1: a server whose agent never came up is no longer reported as running

**Before**

When the on-boot agent install aborted, the VM still reached `running` and the server stayed
there. The server card and the server list returned `status: "running"` with `provisionError`
and `provisionErrorCode` both empty — nothing in those responses told a working server from a
broken one, and billing kept accruing.

```json
{ "status": "running", "blackholeStatus": "NONE", "provisionError": null, "provisionErrorCode": null }
```

**After**

Such a server moves to `error` with an explanation and a new
`provisionErrorCode: "AGENT_NEVER_CONNECTED"`, and its billing is closed out.

```json
{ "status": "error", "blackholeStatus": "NONE", "provisionError": "The server booted, but its agent never connected — …", "provisionErrorCode": "AGENT_NEVER_CONNECTED" }
```

**Impact on integrators**

Nothing to change: `error` is an existing status and no field was added. Worth knowing:

- A readiness wait now terminates — `running` with `blackholeStatus: "NONE"` no longer lasts
  forever. If you poll for `running` + `CONNECTED`, add an exit on `error`.
- Recovery is [`POST /v1/infra/servers/:id/repair`](/docs/infra/lifecycle/repair), which
  reinstalls the agent over SSH. Recreate the server only if repair does not help.
- `POST /v1/infra/servers/:id/start` on such a server answers `422` with the code
  `AGENT_NEVER_CONNECTED`. The status did not change: this machine already got a `422` from `/start`
  (`SERVER_WRONG_STATE`, since it counted as `running`) — the code changes, not the status. Starting it fixes nothing and erases the explanation, which is why it
  is refused. A client that restarts failed servers on a schedule must read the code and call
  repair instead.
- Three lifecycle operations on such a server turn from a success into a refusal, because they
  require `running`: `POST /v1/infra/servers/:id/stop` and `POST /v1/infra/servers/:id/reboot`
  answer `422` with the code `SERVER_WRONG_STATE`, and `POST /v1/infra/servers/:id/sleep-now`
  answers `400` with `NOT_RUNNING`. Stopping such a machine is not needed either: billing is
  closed out with the verdict, so a scheduled stop saves nothing on it. The working actions are
  [`repair`](/docs/infra/lifecycle/repair) and `DELETE /v1/infra/servers/:id` — the same pair the
  response lists in `availableActions`.
- The verdict applies to newly created servers and never touches ones already running. The
  platform checks with the gateway first, so a live tunnel whose notification was lost is
  healed rather than failed. If the agent connects later, the server returns to `running` on
  its own and both fields are cleared.

### FIX-0831-2: key issuance on a free plan now names the cause instead of "retry in a minute"

**Before**

When Bitrix24 refused REST because the account's Bitrix24 plan does not permit it, key issuance and app installation answered `502 CONNECTOR_REST_UNAVAILABLE` with the text "Bitrix24 did not issue the key. Retry in a minute". Retrying never helped: the plan gate does not clear on its own, and the response never said what did block the call.

**After**

The same situation answers `402` with a dedicated paywall code. The `userMessage` field states that a paid Bitrix24 plan is what unlocks the action. Successful responses are unchanged and still return HTTP 200, and every other refusal reason still answers `502 CONNECTOR_REST_UNAVAILABLE`.

### FIX-0831-3: search and research through the Linkup provider

**Before**

Every `POST /v1/search` or `POST /v1/research` call with `provider: "linkup"` was rejected by the provider: the platform did not send the required `outputType` field. For the same reason a Linkup key could not be connected — adding a BYOK key returned `INVALID_CREDENTIAL` with the text `HTTP 400` even for a working key.

**After**

Linkup calls go through, and connecting a BYOK key reflects the real state of that key: a working key is stored, a non-working one is rejected.

In `/v1/search` the `answer` field is filled when `include_answer: true` and is `null` when `include_answer: false`; in that case the page text arrives in `results[].content`. The `results[].published_date` field is always `null` for Linkup — the provider returns no publication date, and the capability matrix in `GET /v1/search/providers` now reports that honestly.

### FIX-0831-4: seamless dashboard sign-in from a signed Bitrix24 form

**Before**

Opening the dashboard from the Bitrix24 app catalog launched the platform address in a new tab. The user landed on the sign-in screen and then on the account picker, even though they had just clicked a button inside their own Bitrix24 account.

**After**

The licence-signed channel gained a second transition surface, `cabinet`. The Bitrix24-side module posts a signed form carrying `BX_VIBE_SURFACE=cabinet` to the new endpoint `POST /microservice/open-cabinet` (the trailing-slash variant is accepted too), and the user arrives in the dashboard already signed in, with no sign-in screen and no account picker.

Surface fields: `BX_VIBE_AUD` — the platform address the form is addressed to (mandatory, compared verbatim); `BX_USER_EMAIL` — the Bitrix24 user's email; `BX_VIBE_NETWORK_STATE` — the network-state hint, self-hosted only; `BX_VIBE_TARGET` — an optional relative path inside the dashboard, `/dashboard` by default. The signed field set and its order, the requirement on each field and the refusal codes are in the integration contract.

The session cookie is not set in response to a cross-site request: a successful response is a page on the platform domain with an auto-submitting form that exchanges a one-time code for the cookie same-site at `POST /api/cabinet/handoff` and forwards the user to the destination path. The code lives 120 seconds and is spent exactly once.

The issued session is an ordinary one but is bound to the Bitrix24 account it came from: switching the active account is refused, and the account listing serves only that one. Two-factor verification, terms consent, account access mode and blocks behave exactly as on an ordinary sign-in.

The surface sits behind a switch and is off by default: while it is off the endpoint answers `404`. The `app` surface (`POST /microservice/open-app`) is unchanged.

### FIX-0831-5: opening an application from the Bitrix24 catalog always acts as an account

**Before**

A Bitrix24 employee with no Vibecode account opened an application on the strength of the Bitrix24 account's signature alone: the platform issued a one-time init code carrying an empty Vibecode user id, and the application received an identity backed by no platform record at all. On a self-hosted portal such an employee opened as a guest, with the name and departments read out of Bitrix24; on cloud the platform asked Bitrix24 `user.get` before the redirect to confirm the employee was live.

**After**

Every open now has an account, and `__init` always carries a non-empty user. On cloud the account is created from the signed `BX_NETWORK_USER_ID` and `BX_USER_EMAIL`. On a self-hosted portal the employee confirms the identity themselves: the platform serves a server-rendered screen for the code mailed to the address from the signed form, and on a correct code the same request links the Bitrix24 member to the Bitrix24.Network profile and opens the application. The outbound `user.get` before the redirect is gone.

The e-mail code is now sent AND checked by Bitrix24.Network itself, through two trusted operations. The first takes the address: the Network finds or creates the profile behind it and mails the code as its ordinary e-mail confirmation. The second takes the same address plus the code and returns the profile for the confirmed address. The platform mails nothing and stores no code. Code lifetime and attempt count are the Network's; the platform keeps its own send-rate caps on top.

`BX_USER_EMAIL` moved out of the `cabinet` surface fields into the common part of the signed set: both platforms send it on both surfaces, right after `BX_NETWORK_USER_ID` (or after `BITRIX_USER_ID` when there is no network id) and before `BX_VIBE_SURFACE`. The field is optional — a form without it is served exactly as before, so an older module keeps working with no changes. The successful response is still a `302` to the application address carrying `__init`.

The account-creating branches sit behind a platform switch that is off by default: while it is off, an open with no account answers with a refusal page linking to the ordinary sign-in, and no partial profiles are left behind.

The "Authorize the application" page is reworked: one button instead of a button plus a quiet link, and once the application is authorized the page forwards to it by itself — no closing the tab and reopening the application from the catalog. The iframe therefore has to allow `allow-popups`. The e-mail confirmation screen opens no new tab: the code field unfolds behind a button on the same page, and a loader covers the card while the platform checks the code and connects the application. The address can no longer be changed there — only the one from the signed form is confirmed, and the single alternative is signing in on the platform yourself, through a link on that same screen. The full field set, the e-mail confirmation screens and the continuation endpoints are in the integration contract.

### FIX-0831-6: Batch create of telephony lines returns the addressable number in results[].id

**Before**

`POST /v1/telephony-lines/batch` with `action: "create"` answered `200`, but `results[].id` carried Bitrix24's internal numeric row id. It could not be used in `PATCH` or `DELETE /v1/telephony-lines/:number` — the line is looked up by number, so the request answered `422`. The single `POST /v1/telephony-lines` already returned the correct number, so the two doors into the same create disagreed.

**After**

`results[].id` is the same number passed in that item's `number` field, and the same value the single [POST /v1/telephony-lines](/docs/telephony/lines/create) returns. It comes back verbatim, including numbers with `+` and other characters. The response is still `200` and no other field changed.

### FIX-0831-7: signing back into Cowork on a fee-free seat no longer fails

**Before**

Cancelling a fee-free Cowork subscription made every later desktop sign-in fail with
`403 COWORK_SUB_CANCELLED`: the cancelled seat was never revived, and a key is only issued
for an active one. There was no way out from inside the app — this path has no browser
route back to a fee-free seat — so the person stayed locked out for good.

**After**

A zero-price seat is revived at sign-in and the key is issued. A paid seat still answers
`403 COWORK_SUB_CANCELLED` / `COWORK_SUB_PAUSED`: resuming it is a payment, and that stays
a deliberate action in the dashboard.

### FIX-0831-8: `TOKEN_MISSING` for a personal key now names the webhook mint failure

**Before**

When a personal key (`vibe_api_*`) had no Bitrix24 webhook because Bitrix24 had refused
the key owner the right to create incoming webhooks, or because the last mint attempt had
failed, the `401 TOKEN_MISSING` response still reported a generic reason (for example
`WEBHOOK_NOT_CONFIGURED`) and pointed at `GET /v1/me`, without explaining that no webhook
was coming or who needed to act.

**After**

In these two scenarios `error.details.reason` now returns `WEBHOOK_MINT_REFUSED_BY_PORTAL`
(a Bitrix24 account administrator must act) or `WEBHOOK_MINT_FAILED` (the platform retries on its
own), and `error.message` names the cause and the addressee explicitly. For
`WEBHOOK_MINT_REFUSED_BY_PORTAL` the reconnect advice (`POST /api/keys/:id/reconnect`) is no
longer included — the platform mints the webhook automatically once a Bitrix24 account administrator
opens the right to create incoming webhooks, and retrying reconnect will not help. The
response code, HTTP status and `error` shape are unchanged.

**Impact on integrators**

Handle `WEBHOOK_MINT_REFUSED_BY_PORTAL` and `WEBHOOK_MINT_FAILED` as distinct
`details.reason` values alongside the already-documented ones — do not recreate the key or
loop on `reconnect` for these reasons; wait for the Bitrix24 account administrator or the platform's
automatic retry.

A separate note on the message text. `error.message` now branches on the reason and on
whether reconnect is available for the key — and this affects more than the two new
reasons. A key with no Bitrix24 scopes (`VIBE_SCOPES_ONLY`) gets its own wording, and keys
the platform would refuse reconnect for (cowork keys, app keys, keys bound to a server or
to a live agent) get advice to create a new key instead of advice to reconnect. The
`details.reason` values in those scenarios are unchanged. If your code matches on
`error.message` as a string, switch to `details.reason`: the message text is not a
contract and does change.

### FIX-0831-9: vibe top-up is open to self-hosted accounts on the international installation

**Before**

A self-hosted account on the international installation always got the
`BOX_TOPUP_NOT_AVAILABLE` refusal. The subscription preview
`GET /v1/cowork/subscription/preview` returned `topUpAvailable: false` for such
an account, and the package catalogue came back empty.

**After**

A self-hosted account on the international installation tops up just like a
cloud one — it gets the package catalogue, a payment link and
`topUpAvailable: true` in the subscription preview. The
`BOX_TOPUP_NOT_AVAILABLE` refusal remains only where top-up is closed to
self-hosted accounts in the account's region. The response format is unchanged
and the successful response remains HTTP 200.
