# API changes: August 21, 2026

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

### FIX-0821-1: iblockTypeId=structure is accepted on /v1/lists

**Before**

`iblockTypeId` accepted only `lists`, `lists_socnet`, and `bitrix_processes`. The value `structure` (the absence-calendar infoblock type) returned `400 INVALID_IBLOCK_TYPE`, even though `GET /v1/lists/:iblockId/type` returned it.

**After**

`structure` is in the allowed set on every `/v1/lists` route. The Bitrix24 response for that type (data or `422`/`403`) is no longer replaced by our `400`. Other unknown types still return `400 INVALID_IBLOCK_TYPE`. The default is unchanged: `lists`.

**Impact on integrations**

Requests to `structure` infoblocks, including the stock `absence` calendar, now reach Bitrix24. Existing integrations that use other types require no changes.

### FIX-0821-2: currencyId and explicit selection of non-returned fields

**Before**

[POST /v1/products](/docs/entities/products/create) and [PATCH /v1/products/:id](/docs/entities/products/update) accepted the currency only under the `currency` name. An explicit selection of a field marked `notReturned: true` was accepted without a warning even though the response contained no value.

**After**

Product write methods accept `currencyId` as an additional name for `currency`. If both names are present, `currency` wins, and responses still carry only `currency`. The additional name is marked `writeOnly` and `notReturned` in `/fields`, `/v1/guide`, and OpenAPI. Explicitly selecting any non-returned canonical name now adds an `UNKNOWN_SELECT_FIELD` warning: `products.currencyId`, `tasks.realStatus`, `product-sections.sort`, `bank-details.entityTypeId`, `telephony-lines.serverName`, `bizproc-templates.templateData`, and `catalog-products.iblockSection`. The native Bitrix24 name `select=CURRENCY_ID` continues to select the readable `products.currency` field. Each name is covered by the field references for [products](/docs/entities/products/fields), [tasks](/docs/entities/tasks/fields), [product sections](/docs/entities/product-sections/fields), [bank details](/docs/entities/bank-details/fields), [telephony lines](/docs/telephony/lines/fields), [business process templates](/docs/entities/bizproc-templates/fields), and [catalog products](/docs/entities/catalog-products/fields). Currency filtering remains unsupported and returns `400 UNSUPPORTED_FILTER`.

**Impact on integrators**

Read requests still return `200` and the other selected fields, but now carry a warning. Remove non-returned names from `select`: use `currency` for the product currency and `status` for the task's actual status.

### FIX-0821-3: product description filter is no longer rejected

**Before**

[GET /v1/products](/docs/entities/products/list) with `filter[description]` and [POST /v1/products/search](/docs/entities/products/search) with the same filter returned `400 UNSUPPORTED_FILTER`, even though the description was stored when the product was created.

**After**

Exact match and `$in` on `description` are accepted the same way as on `name`. Operators, including `$contains`, still return `400`. The `price` and `currency` filters are unchanged.

### FIX-0821-4: the refusal on creating an employee without a department now names the field to pass

**Before**

`POST /v1/users` without `departmentId` on an account with the extranet module installed answered `422` with the text `no_extranet_field`. No field of that name exists in the request body or in the `GET /v1/users/fields` output, and nothing in the response pointed at the department — the refusal gave no way to tell what to correct. The employee was not created.

**After**

The response now carries a hint: it names the field in both spellings — `UF_DEPARTMENT` for a direct call and `departmentId` for the `POST /v1/users` wrapper (both are accepted) — points at `GET /v1/departments` as the source of values, gives the root department as a working example, and mentions `POST /v1/users/invite`, which supplies the department itself. For an external user the alternative is named — `EXTRANET` together with `SONET_GROUP_ID` instead of a department. The `departmentId` description in the field reference now states the requirement too, so the condition is visible before the request is sent.

**What integrators should do**

No action is required: the status code and the envelope shape are unchanged, only the hint was added. The `departmentId` field is deliberately not marked mandatory on the Vibecode API side — an account without the extranet module accepts a create with no department, and a hard requirement would break those requests.

### NEW-0821-5: Open Channels dialog metadata via the API

A new endpoint [POST /v1/openlines/dialogs/lookup](/docs/openlines/dialog) — a wrapper over the Bitrix24 method `imopenlines.dialog.get`. It returns the card of a single Open Channels dialog (name, type, line, message count, dates) by one of the identifiers: `chatId`, `dialogId` or `sessionId`. It does not return the message history. Requires the `imopenlines` scope.

The response carries a derived `lineId` field — the line identifier parsed from the dialog binding; it is the entry point to [GET /v1/openline-configs/:id](/docs/openlines/config/get). Lookup by `sessionId` resolves a dialog straight from a session identifier taken from the `id` field of [POST /v1/openlines/sessions/search](/docs/openlines/sessions).

### BC-0821-6: a structured value in a scalar field is refused instead of silently lost

> Old format supported until: not provided

**Before**

An entity write accepted an object or an array in a field declared scalar and answered with success. [POST /v1/deals](/docs/entities/deals/create) carrying `{"title": {"a": 1}}` returned 201 while the deal card showed the string `Array` — that is how Bitrix24 casts an array to a string. The value could not be recovered and the client saw no error.

**After**

Such a request is refused before the Bitrix24 call — `400` with code `INVALID_PARAMS` and the field name. The rule covers fields declared `string`, `number`, `boolean`, `date` and `datetime`, on entity writes under `/v1/<entity>`: create and update, [POST /v1/batch](/docs/batch), per-entity batch write and import.

What did NOT change: a number or a boolean in a string field is still accepted (Bitrix24 stores `123456` and `1`, so nothing is lost), `null` is still accepted, and fields declared `object`, `array` or multi-value accepted structures before and still do.

**Impact on integrators**

Review any code that builds a write body from an external source: where an object or an array reached a scalar field, the request used to succeed while losing the value and will now return `400`. Send a scalar value to such a field.

The boundaries are stated explicitly. A name absent from the entity schema (user fields `UF_*`, `propertyNNN`, a typo) is not checked — it has no declared type. The refusal shape differs by surface, and so does its reach. The global batch refuses only its own sub-call and puts the failure in `data.errors["<id>"]` with separate `code` and `message` fields. The per-entity batch write and import refuse the whole request: a `400` with code `BATCH_ITEM_VALIDATION` or `IMPORT_ITEM_VALIDATION`, with the element index and `INVALID_PARAMS` inside `message`. Such a response carries no per-item results, and nothing reaches Bitrix24. A few bespoke write routes are not covered yet — addresses, task comments, document templates, Open Channels config and product rows; there the previous behaviour still applies, and they are closed separately.

### FIX-0821-7: PATCH /v1/infra/servers/:id/access-policy now preserves the access list on policy change

**Before**

Switching [PATCH /v1/infra/servers/:id/access-policy](/docs/infra/access/access-policy) away from `NAMED_USERS` or `DEPARTMENT` to any other policy permanently deleted the access list (users and departments added via `POST /access`) — even though the docs promised the records stay in the database and simply stop applying until the policy becomes a named one again.

**After**

The list is preserved when leaving `NAMED_USERS`/`DEPARTMENT` — behavior matches the documentation again.

**Impact on integrators**

No action required — behavior now matches the already-published documentation.

### BC-0821-8: a busy shared galaxy exec channel now answers 409 instead of 502

> Old format supported until: not provided

**Before**

[POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) for an application in a galaxy (`kind: "GALAXY_APP"`) answered `502` with the `EXEC_BUSY` code when the host's shared exec channel was busy. The response carried neither a `Retry-After` header nor the `retryable` / `retryAfter` fields, so a machine client read the refusal as a gateway failure and did not retry. The same refusal on [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) already arrived as `409` with a retry signal.

The `error.hint` in `409 EXEC_BUSY` responses on galaxy routes offered to unstick the channel via `POST /v1/infra/servers/:id/unstick`. That call is not available to an application owner or a host owner by contract — it answers `409 GALAXY_UNSTICK_UNSUPPORTED`, because the exec channel is shared by every application on the host.

**After**

A busy shared host exec channel arrives as `409` with the `EXEC_BUSY` code, a `Retry-After` header and the `retryable: true` / `retryAfter` (seconds) fields — the same contract as every other "busy" refusal. Other execution failures for an application in a galaxy still arrive with status `502`.

Two different states now share the `409` status, and they are machine-distinguishable by the presence of `error.hint.autoExpiresInSeconds`: the application's own lock carries it (the remaining lock TTL), a busy shared host channel does not. `recoveryAction` cannot tell them apart: on a galaxy application and on the galaxy host itself that field names no concrete call at all — only "wait" and "retry" — because releasing a lock there can abort a running operation and, on a shared host, the commands of neighbouring applications with it. The machine-actionable advice is therefore the same in both states — wait and retry at the `Retry-After` interval.

**Important:** this is said about the galaxy states, not about the `/exec` route as a whole. On a dedicated virtual machine (`kind: "STANDALONE"`) the `recoveryAction` of the same `409` still names `DELETE /v1/infra/servers/:id/lock`, unconditionally — and against a running deploy that call removes the per-server serialization the deploy relies on. So `recoveryAction` must never be executed without reading `error.hint.recovery` first, in any state. The lock-release address stays in `error.hint.recovery`, with the server identifier substituted, the owning key required and the caveat that it is only for when you have confirmed nothing is running.

Reading the log answers by the same contract now. [GET /v1/infra/servers/:id/logs](/docs/infra/deploy/logs) for an application in a galaxy goes through that same shared host exec channel, and with the channel busy it used to answer `200` with an empty `logs` list — claiming the application had no output. The busy state now arrives as `409` with `Retry-After`, and other read failures as `502` carrying the agent's code.

**What integrators should do**

A client that branched on the HTTP status and treated `502` on `/exec` as a terminal failure must stop doing so: the busy state now arrives as `409` and should be retried at the `Retry-After` interval. A client that read `error.code` needs no change. If your flow called `POST /v1/infra/servers/:id/unstick` because the platform suggested it, that call never worked on galaxy servers — replace it with a retry, and contact support if the refusal persists. If you branch between the two `409` states of `/exec` in code, key on the presence of `error.hint.autoExpiresInSeconds` rather than on the text of `recoveryAction`.

### BC-0821-9: the per-entity batch now explains what it did with select

> Old format supported until: not provided

**Before**

`POST /v1/{entity}/batch` with the `list` action applied `select` to the response but said
nothing about what it dropped. A name the entity does not have simply vanished: the record came
back narrowed and no explanation existed anywhere — no error, no warning. The single list and the
[global batch](/docs/batch) both report such a name as a warning; this door was the only one where
field selection was completely silent.

**After**

The sub-call carries its own `meta.warnings` with the `UNKNOWN_SELECT_FIELD` code and the field
name — the same shape the single list uses. The warning belongs to ITS sub-call: neighbouring
items get no `meta` of their own, and the key never arrives as an empty array — nothing to say
means no key.

Entities where an unknown name answers with an error (today, calendar events) now refuse such a
sub-call here too — `UNKNOWN_SELECT_FIELD` before the Bitrix24 call. The refusal is per-call, as
in the global batch: only that item fails, the other forty-nine still run. Before, the hard
selection guard did not fire on this door at all.

The value `*` still means "return every field": no selection is applied and an unknown name next
to it refuses nothing. A warning still arrives: a `select` of `*,titel` returns every field AND
says the second name means nothing — exactly as on the single list and in the global batch.

**Impact on integrators**

The additive half breaks nothing: `meta` is a new optional key next to `data` and `total`. The
breaking half is the hard selection: a sub-call that used to answer with a narrowed record and
code `200` now answers with an `error` in its slot on calendar events. There is deliberately no
support window: the previous behaviour was itself the defect — the requested name vanished
silently, with nothing to tell that apart from "the field is not in the record". Review handlers
that treated the absence of an error as proof that every `select` name was recognised.

### NEW-0821-10: Open Channels session transcript via the API

A new endpoint [POST /v1/openlines/sessions/history](/docs/openlines/history) — a wrapper over the Bitrix24 method `imopenlines.session.history.get`. It returns the message history of a chat's latest Open Channels session by the chat identifier: messages, participants and file metadata in one response. Requires the `imopenlines` scope.

Input is by the chat identifier only (`chatId`, the `chat2043` form is accepted too). By it the latest session of the chat is taken. The method has no pagination — the transcript comes in full. For page-by-page reading of messages use [GET /v1/chats/:dialogId/messages](/docs/chats/messages/list).

The endpoint is enabled gradually by the Vibecode platform: while it is off, the call answers `403 OPENLINES_HISTORY_DISABLED` — a sign the capability is not active yet, not an integration error.

### NEW-0821-11: galaxy app multipart deploy stores the archive in source storage

The multipart deploy of a galaxy app no longer buffers the whole archive in platform memory: where
multipart depositing is enabled, the archive is stored as a version in the
[source storage](/docs/source-storage) and reaches the host through a signed link. Where the host
is allowed to fetch the archive itself, a recognised tar.gz is downloaded by the host — verifying
the size and checksum recorded for that version — and a failed fetch then arrives as a separate
`UPLOAD_DOWNLOAD_FAILED` / `UPLOAD_EXTRACT_FAILED` / `UPLOAD_NO_SPACE` code in the `buildLog` tail;
a zip and an unrecognised format keep the previous path, through the agent. The size limit on an
archive sent in the request body is unchanged — the body still travels through the platform. The
version stays in the depot even when the deploy fails: it is listed among the versions and can be
redeployed by `source.versionId`.
