# API changes: July 21, 2026

[← Changelog](/docs/changelog) · [July 2026](/docs/changelog/2026-07)

### FIX-0721-1: built-in search engine name and description, cloud provider name

The public name of the platform search engine now matches the product name: `GET /v1/search/providers`
and `/v1/me` return `Bitrix24 AI Search` in the `name` field (latin locales). The provider identifier
`bitrix-search` is unchanged — no client action is required.

The source-citation claim was removed from the same provider's description: the capability depends on
the engine bound to your instance and is declared machine-readably in `capabilities.output.citations`
of the same response.

`GET /v1/infra/providers` on the international segment now returns the `Bitrix24 Cloud` brand in the
`name` field instead of `Bitrix Cloud`. The provider identifier `bitrix-cloud` is unchanged.

**Before**

`"name": "Bitrix AI Search"` · `"description": "Platform AI search with agentic mode and source citations"` · `"name": "Bitrix Cloud"`

**After**

`"name": "Bitrix24 AI Search"` · `"description": "Platform AI search with agentic mode"` · `"name": "Bitrix24 Cloud"`

### FIX-0721-2: creating a business-process template now accepts the template file

**Before**

[POST /v1/bizproc-templates](/docs/entities/bizproc-templates) answered `422 Incorrect field TEMPLATE_DATA!` for any body — creating a template was impossible: the field holding the `.bpt` file content was not in the entity schema and never reached Bitrix24.

**After**

The `templateData` field (a `.bpt` file as a `[filename, base64 content]` array) is accepted and forwarded to Bitrix24, and the template is created. The field is required on create: without it the request is rejected with `400 MISSING_REQUIRED_FIELDS` before Bitrix24 is called (previously the raw `Incorrect field TEMPLATE_DATA!` error came back). It is write-only and surfaces in the field reference at `GET /v1/bizproc-templates/fields`, but is not returned on reads.

### NEW-0721-3: Aggregated source registry — GET /v1/me/sources

A new endpoint [GET /v1/me/sources](/docs/source-storage) — the programmatic twin of the dashboard "Application sources" page. It returns source snapshots across every server and application the key owns (an account-admin key sees the whole account), with pagination (`page`/`limit`/`search`) and the standard `{ success, data, total, page, limit }` envelope. Each row carries a drill-in pointer — `listEndpoint` and `latestDownloadEndpoint` — plus `reachableViaApi` and, for server rows, `blackholeStatus`. Unlike [GET /v1/infra/servers](/docs/infra/servers/list), which is scoped to the calling key's own servers, this registry also spans a server bound to another key of the same owner.

The server-scoped source endpoints ([POST /v1/infra/servers/:id/sources](/docs/source-storage) and the sibling list/download/tag/cleanup routes) are now documented, and the `versions[].serverContext` field (`{ serverId, serverName, serverDisplayName, linkedApp }`) is formalized in the contract.

### NEW-0721-4: optional error.b24Code field in 422 BITRIX_ERROR responses

`422 BITRIX_ERROR` responses now include an optional `error.b24Code` field — the raw Bitrix24 error code for programmatic handling (for example, `PERIOD_REQUIRED`, `INVALID_FILTER`). The change is additive: existing clients that parse only `error.code` and `error.message` are not affected.

### NEW-0721-5: Open Channels statistics — 6 dashboard methods

A new API section for contact-center dashboards: [POST /v1/openlines/stats](/docs/openlines/stats) (period aggregates), [GET /v1/openlines/operators](/docs/openlines/operators) (real-time operator load), [POST /v1/openlines/sessions/search](/docs/openlines/sessions), [POST /v1/openlines/sessions/stats](/docs/openlines/sessions/stats), [POST /v1/openlines/sessions/transfers](/docs/openlines/sessions/transfers), [POST /v1/openlines/ratings/search](/docs/openlines/ratings). Requires the `imopenlines` scope and the `report_open_lines` plan feature (otherwise `403 B24_TARIFF_RESTRICTION`).

**Rolling out** — the methods ship with the Bitrix24 update `imopenlines 26.700.0` and are not yet available on every account. Until the update reaches your account, the methods return `422 METHOD_NOT_YET_AVAILABLE` with the target version in the response — this indicates the rollout, not an integration error.

### FIX-0721-6: a Bitrix24 plan refusal is returned as 403 B24_TARIFF_RESTRICTION on every endpoint

**Before**

A Bitrix24 refusal caused by a plan restriction arrived as `422 BITRIX_ERROR` with an opaque message — there was no way to tell it apart from other Bitrix24 errors programmatically.

**After**

Such a refusal is returned as `403` with the `B24_TARIFF_RESTRICTION` code. The rule is platform-wide, not limited to Open Channels: any endpoint that calls a Bitrix24 method unavailable on the Bitrix24 account plan now answers with this code.

**Impact on integrators**

Clients with generic error handling keep working unchanged — the refusal is still an error, only a more precise one. If you branched on `422` specifically for plan refusals, move that branch to `403` and `error.code === 'B24_TARIFF_RESTRICTION'`. This code does not mean the integration is broken: the capability is not included in the Bitrix24 account plan, and retrying is pointless until the plan changes.

### FIX-0721-7: Versioned deploy runtimes install the advertised version on Ubuntu 24.04

**Before**

The `node20` runtime installed Node.js 18 (Ubuntu 24.04 has no Node 20 package), and `python311` plus the RAG runtimes (`node20-rag`, `python311-rag`) failed at the install step because the packages are absent from the distribution. The `GET /v1/infra/runtimes` `packages` field advertised `postgresql-14` while PostgreSQL 16 was installed.

**After**

`node20` now installs Node.js 20 (with a major-version check), `python311` installs Python 3.11, and the RAG runtimes install PostgreSQL 16 with the pgvector extension in the application database. The `packages` field reflects the real version (`postgresql-16`). Runtime identifiers and the `/deploy` request format are unchanged.

### FIX-0721-8: server repair status is correct when polled

**Before**

Polling [GET /v1/infra/servers/:id/repair-status](/docs/infra/lifecycle/repair-status) on a multi-node backend could briefly return `idle` even while the repair was still running — when the request landed on a different serving node than the one running the repair. A client polling the status in a loop could therefore wrongly conclude the repair had finished before it even started.

**After**

The endpoint reliably returns the real repair progress (`running` / `done` / `failed`) regardless of which node the poll lands on.

**Impact on integrators**

The response shape is unchanged and no client action is required.

### FIX-0721-9: apps on a standalone server no longer run with administrator privileges

**Before**

An app deployed to a standalone Black Hole server ran with administrator privileges and no isolation. Any vulnerability in the app itself (remote code execution, for example) immediately meant full control of the whole virtual machine: the server's connection keys, its service configuration and system files.

**After**

The app runs under a dedicated unprivileged account and sees only its own directory (`extractTo`, `/opt/app` by default), which it owns. System directories are read-only to it and privilege escalation is blocked. Ports below 1024 still work — the platform grants the specific permission needed.

Deploy commands (`install`, `preStart`) and `/exec` still run with administrator privileges. Nothing changed there and no `sudo` is needed.

No action required: if your app genuinely needs administrator privileges to start, the deploy automatically restores the previous mode, finishes successfully and adds a warning explaining why. To skip that attempt outright — relevant for nginx as the start command, MySQL over the root system socket, and Docker-driven starts — pass `"hardening": "off"` in the deploy body.

Two new `step` values appear in `data.steps[]`: `service_user` for handing the deploy directory to the unprivileged account, and `hardening` for the warning that the app was restored to the previous mode. Clients that switch on step names should account for them.

### BC-0721-10: a broken image_url candidate no longer fails the whole request

> Old format supported until: 21.01.2027

**Before**

A `content` array could carry several `image_url` parts. If any one of them held
something other than an image — for example a Base64-encoded HTML error page
declared as `image/png` — the platform forwarded it to the model unchanged. The
model could not decode it and the entire request failed with `502` and code
`ai_provider_unavailable`, even when the remaining images were valid.

Separately, parts with an unsupported MIME type, malformed Base64, or above the
20 MiB limit were rejected with `400 invalid_image_payload` — also for the whole
request.

**After**

Before calling the model, the platform inspects the actual content of every
`image_url` part by its byte signature rather than its declared MIME type. A part
whose content is a web response (HTML, XML, JSON, an HTTP response) or does not
decode is replaced in place with the text placeholder
`[image unavailable: <reason>]`. The remaining images are processed normally and the
request succeeds.

Positions are preserved: the length of the `content` array does not change, so any
candidate numbering on your side stays correct.

Rejected parts are visible in the response — `warnings` gains an entry with code
`IMAGE_CONTENT_REJECTED`, and the `X-Image-Parts-Rejected` header carries their
count. For streaming responses the header arrives with the start of the stream.

`400` is now returned only for structural errors: a missing `url` field, a string
that is neither a URL nor a data URI, an unsupported scheme, and `http://` in
production.

**What integrators should do**

If your code relied on `400 invalid_image_payload` to detect a rejected image, read
`warnings` or the `X-Image-Parts-Rejected` header instead. The request now succeeds,
and no image is dropped silently — every substitution is reflected in the response.

Model-side failures changed too. Previously any non-2xx provider response arrived as
`502`; now the status reflects the cause:

- a model response of `400` or `422` → `400` with code `ai_provider_rejected`.
  Retrying such a request unchanged will not help;
- a model-side rate limit (`429`) → `429` with a `Retry-After` header. Retry it after
  the stated delay. A streaming response cannot carry the header, so the delay
  arrives as a `retryAfter` field in the error frame;
- a model-side timeout (`408`) → `503` with code `ai_provider_timeout`.

Responses `401`, `403` and `5xx` still arrive as `502`. This affects
[POST /v1/chat/completions](/docs/ai/chat/completions) and
[POST /v1/embeddings](/docs/ai/embeddings).

Model-side errors now additionally carry a `providerStatusCode` field — the raw
HTTP status of the model response. It tells a model-side `429` apart from the
platform's own rate-limit `429` (which has no such field): both keep the same
`rate_limit_exceeded` code so SDKs retry uniformly, and the new optional field is
the distinguisher.

Data URIs may now also carry parameters between the type and `;base64` —
`data:image/jpeg;name=photo.jpg;base64,...` is no longer rejected.
