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