# API changes: June 30, 2026

[← Changelog](/docs/changelog) · [June 2026](/docs/changelog/2026-06)

### NEW-0630-1: POST /v1/cowork/deploy-key — get a project deploy key from Cowork/Code

The Cowork/Code key (`vibe:cowork`) is data-plane only and is blocked from the infrastructure control-plane with 403 `INFRA_FORBIDDEN_FOR_COWORK_KEY`. The new [POST /v1/cowork/deploy-key](/docs/cowork) endpoint lets an agent self-serve a separate project deploy key: call it with the same Cowork key, take the top-level `key` field from the response (a bare object, not wrapped in `data`), and use it as the `X-Api-Key` header for deploy/provision/exec under `/v1/infra/*`.

The returned key carries the `vibe:infra` + `vibe:storage` scopes (no `vibe:cowork`), expires in 7 days, and is bound to the Cowork key's owner and account. Each call returns a fresh key and revokes the previous project key (exactly one is active). Requires the `vibe:cowork` scope and an active Cowork/Code subscription; rejection codes are 403 `INSUFFICIENT_SCOPE` / 403 `COWORK_NOT_ACTIVATED` / 503 `DEPLOY_KEY_DISABLED` / 503 `INFRA_DISABLED`.

### NEW-0630-2: Self-hosted placement now receives a one-time authorization code on appUrl

For an app with its own `appUrl` (outside Black Hole) opened as a placement, the platform now appends a one-time authorization code (`?code=...`) to the `appUrl` redirect instead of an internal gateway token. The app exchanges this code for a `vibe_session` via the existing `POST /v1/oauth/token` — `redirect_uri` must exactly match the configured `appUrl`. Previously such apps received a non-redeemable token and could not authorize the user.

### NEW-0630-3: New endpoint POST /v1/oauth/placement-session for self-hosted apps

A self-hosted app (on its own server, not on Black Hole) opened as a placement (iframe) in Bitrix24 can now exchange the Bitrix24 user token it received in the placement callback on its own handler for a `vibe_session`. The request `POST /v1/oauth/placement-session` with body `{ app_key, access_token, member_id, domain }` (optionally `refresh_token`, `expires_in`) is server-to-server — the session token never reaches the browser. The authorization section of the docs covers both placement topologies.

### FIX-0630-4: PATCH on an OAuth application key's scopes: honest rejection instead of false access

**Before**

`PATCH /v1/keys/:id` adding a Bitrix24 scope to an OAuth application key (`vibe_app_*`), and `PATCH /v1/apps/:id` widening an app's `scopes`, returned `200` and persisted the new scope set. But an OAuth application's scopes are fixed at issue time and such an edit never reaches the Bitrix24 side, so `GET /v1/me` then reported a scope Bitrix24 had not granted and the actual call was rejected.

**After**

Adding a Bitrix24 scope to an OAuth application key or to an app is now rejected with `403 OAUTH_SCOPE_CHANGE_REQUIRES_REISSUE`. Removing scopes and changing `vibe:*` scopes still work. To gain a new scope, issue a new authorization key with the scopes you need.

### FIX-0630-5: a key with the `tasks` scope now actually unlocks Tasks methods

**Before**

A key issued with the `tasks` scope (plural — the spelling the UI picker offers) minted, on a dev-key account, a webhook that Bitrix24 accepted but never bound to the Tasks REST methods. `GET /v1/me` reported `tasks`, yet Tasks method calls were rejected on the Bitrix24 side. The `task` scope (singular) worked.

**After**

On key issue and edit the scope set is canonicalised to the spelling Bitrix24 actually binds methods to (`tasks` → `task`), so the key unlocks Tasks methods regardless of the chosen spelling. Already-issued keys are not changed retroactively — re-issue the key.

### FIX-0630-6: a malformed FILES field on a timeline comment is now rejected with 400

**Before**

[POST /v1/timelines](/docs/entities/timelines/create) and `PATCH /v1/timelines/:id` accepted the `FILES` field in any shape and answered `200`/`201`. When the shape was anything other than an array of `[[fileName, base64Content]]` pairs — a flat array of strings or a single pair without the outer array — the comment was created but the file was attached as garbage (random name, unreadable content) or silently dropped, with no error.

**After**

A `FILES` value that is not an array of `[[fileName, base64Content]]` pairs is rejected before the Bitrix24 call with `400 INVALID_FILES_SHAPE` and a hint about the correct shape. An empty `FILES` (`[]`) and an omitted field are still accepted. The check applies to single `POST`/`PATCH` and to batch (`POST /v1/batch`, `POST /v1/timelines/batch`).

**Impact on integrators**

Callers passing `FILES` in the documented `[[fileName, base64Content]]` shape are unaffected. Callers relying on other shapes now get an explicit `400` instead of a silently broken attachment, and can fix the request.

### FIX-0630-7: empty bot and user fields now return null/[] instead of false/{}

**Before**

In bot-card responses ([GET /v1/bots/:botId](/docs/bots/management/get), [POST /v1/bots](/docs/bots/management/create), [PATCH /v1/bots/:botId](/docs/bots/management/update)) the unset fields inside `users[]` came back as the wrong primitive: the datetimes `lastActivityDate`, `mobileLastDate`, `desktopLastDate` were returned as boolean `false`, and an empty `phones` list was also `false`. The same `lastActivityDate` field in `GET /v1/users` came back as an empty object `{}`. As a result `new Date(lastActivityDate)` silently produced the epoch and `phones.map(...)` threw a type error.

**After**

An unset datetime is now encoded uniformly as `null` on every path, and an empty phone list as `[]`. A populated datetime is still an ISO string and a populated list is still an array.

**Impact on integrators**

No action required — the types are now correct. Code that relied on comparing empty values to `false` will stop matching: check the datetime against `null` and treat the phone list as an array.

**Affected endpoints:** [GET /v1/bots/:botId](/docs/bots/management/get), [POST /v1/bots](/docs/bots/management/create), [PATCH /v1/bots/:botId](/docs/bots/management/update), `GET /v1/users`

### NEW-0630-8: Relink an app's OAuth credentials without deleting it

A new endpoint [POST /v1/apps/:id/relink-oauth](/docs/apps) updates `bitrixClientId` and `bitrixClientSecret` on an existing application without deleting it. This is needed when the local OAuth application is recreated in the Bitrix24 account and its `client_id` changes: previously the only path was to delete the app (which broke the linked bot, Open Channels config and bindings) and create it anew.

Request body: `{ bitrixClientId, bitrixClientSecret }` (both required). The paired key, bot and bindings are preserved. If that `client_id` is already linked to another application — `409 OAUTH_CLIENT_ID_IN_USE`. It cannot be called with the OAuth application's own key — `403 OAUTH_APP_KEY_CANNOT_RELINK` (use a personal key or the dashboard). After relinking, reinstall the app in the account — this restores the event subscription.

### NEW-0630-9: Web search: full page text, images, news mode, and research domain filters

**Before**

`POST /v1/search` accepted `include_raw_content` as a boolean flag, but the full text of the found pages did not reach the response. There were no parameters for news mode or for requesting images. `POST /v1/research` accepted `include_domains` and `exclude_domains` but silently dropped them.

**After**

`POST /v1/search` gained two new optional parameters: `topic` (`general` or `news`, default `general`) and `include_images` (boolean, default `false`). The boolean `include_raw_content` now actually returns the full text: each result gained a `rawContent` field (the full page text, size-capped). The response added top-level `images` (an array of objects with a `url` field) and `ignored_filters` (a string array — the passed filters the provider could not apply). These fields arrive in both the synchronous response body and the `done` streaming frame. The `X-Search-Filters-Ignored` header is kept and may now list `topic` and `include_images`. `POST /v1/research` now applies `include_domains` and `exclude_domains` (up to 20 each) for capable providers and reports overflow via `ignored_filters` in the `done` frame. Which capabilities a selected engine offers is returned by `GET /v1/search/providers`. Clients with strict schema validation via `additionalProperties` should account for the new response fields.

### NEW-0630-10: Read AI client-call transcripts via the API

The new [GET /v1/activities/:activityId/transcript](/docs/entities/activities/transcript) endpoint returns the ready-made AI transcript of a client call by the ID of its CRM Call activity. The method only reads an existing transcript — it does not trigger generation. It requires the `crm` scope. When there is no transcript for the call yet, the `data.transcription` field is `null` — a normal response, not an error.
