# API changes: August 4, 2026

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

### BC-0804-1: V1 /wake and /start wake galaxy apps via the host

> Old format supported until: 22.08.2026

**Before**

`POST /v1/infra/servers/:id/wake` and `/start` for `kind=GALAXY_APP` returned **422 `VM_MISSING`** (no cloud VM) and suggested redeploy even when the container was only idle-sleeping. Scheduled jobs could not recover via API after the first sleep.

**After**

For a placed galaxy app both verbs use host-mediated wake (same as the dashboard): wake the shared host if needed, then start the container. Success is **HTTP 200**. Galaxy `?wait=true` does **not** wait until RUNNING with `WAKE_TIMEOUT` — after a cold wake the app may still be SLEEPING; poll GET. Host `preventWake` blocks **both** `/wake` and `/start` (**403 `SERVER_WAKE_BLOCKED`** or the **402** paywall code `INT_TARIFF_REQUIRED` — not freeze markers like `TRIAL_EXPIRED`). A slot without host/`galaxyId` → **404 `GALAXY_HOST_NOT_FOUND`** (not 422 `VM_MISSING`). STANDALONE `VM_MISSING` / `/start` override behaviour is unchanged.

### BC-0804-2: the declared archive type is now checked against the first bytes

> Old format supported until: 03.08.2026

**Before**

Saving a source version — [POST /v1/infra/servers/:id/sources](/docs/source-storage) and [POST /v1/apps/:id/sources](/docs/source-storage) — trusted the `Content-Type` header. A zip archive sent with `Content-Type: application/gzip` was accepted, stored as gzip and named with a `.tar.gz` extension; deploying that version to a server then failed during extraction with an opaque archive-read error. Auto-saving a version after a deploy always recorded the format as gzip, whatever the request body actually contained.

**After**

The first bytes of the archive are read on intake. A direct contradiction between the declared type and the content — `application/gzip` declared while the bytes are zip, or the other way round — is rejected with `415 UNSUPPORTED_ARCHIVE_FORMAT`; the response body carries `error.hint.declared` and `error.hint.detected`. The types `application/x-tar` and `application/octet-stream` never trigger this refusal: their signature is not readable within the first eight bytes, so no contradiction can be established. Unrecognised content is accepted as before.

Auto-save after a deploy never refuses: the client declares no archive type there, so the recognised bytes are simply recorded honestly — a zip is stored as a zip and goes to the right extractor on the next deploy.

**What integrators should do**

Send the `Content-Type` that matches the archive (`application/gzip` for tar.gz, `application/zip` for zip), or `application/octet-stream` when the type is unknown. Clients already sending the correct header need no change.

### NEW-0804-3: field names and descriptions for product sections, requisite presets and bank details

**What is new**

`GET /v1/product-sections/fields`, `GET /v1/requisite-presets/fields` and `GET /v1/bank-details/fields` used to return only `type` and `readonly` — not a single field had a human-readable name, so the reference could not tell you what, say, `rqAccNum` means. Names are now declared for every field: 7 for product sections, 11 for requisite presets, 34 for bank details. For requisite presets and bank details a description arrives alongside the name.

The response gains `label` and `description` keys next to the existing `type` and `readonly` — no previously returned field changed.

The dynamic Bitrix24 reference could not supply these names: it only fills in fields absent from the static schema, so for a declared field its own labels were discarded. The `aggregate` operation on bank details stays disabled.

### NEW-0804-4: `limit=0` is no longer ignored silently

**What is new**

`limit=0` is not a page size: the parameter is dropped and the default page size applies. That used to happen without any signal — the response came back 200 with a full page of records, as if the parameter had been honoured. Such a response now carries a warning in `meta.warnings`:

```json
{
  "meta": {
    "warnings": [
      {
        "code": "LIMIT_ZERO_IGNORED",
        "field": "limit",
        "message": "limit=0 is not a page size and was ignored. Valid range: 1..5000; pass an explicit limit (e.g. 5000) to read the whole collection."
      }
    ]
  }
}
```

It applies to `GET /v1/{entity}` and `POST /v1/{entity}/search` for every entity. The applied value itself has not changed — existing calls keep returning the same number of records as before; to read a whole collection pass an explicit `limit` (5000 maximum).

### FIX-0804-5: bank details: `entityTypeId` marked as a field that is never returned

**Before**

`GET /v1/bank-details/fields` described `entityTypeId` as an ordinary numeric field — readable and writable. The fact that Bitrix24 accepts the value on create but never returns it on read was stated only in the field description text. A client that generates its model from the machine-readable reference rather than from the prose put the field in its read type and found nothing where a number was expected.

**After**

The field now carries `notReturned: true` — in `GET /v1/bank-details/fields`, in `GET /v1/guide` and in the OpenAPI schema (there as an `x-notReturned` annotation plus a sentence in the description). This release introduced the same flag for `serverName` on telephony lines.

The field stays writable: `POST /v1/bank-details` still accepts `entityTypeId` (always 8 — the owner requisite). The flag speaks only about reading.

**Impact on integrators**

Nothing to change — the flag is additive. If you generate types from the reference, `entityTypeId` can be dropped from the read model and kept in the create model.

### FIX-0804-6: discover names the real path identifier

**Before**

`GET /v1/guide` and the OpenAPI schema advertised the item path as `/:id` for every entity. For four that was untrue: `smart-processes` is addressed by the public `entityTypeId` (1030 and up), `telephony-lines` by the line number, and `bizproc-robots` / `bizproc-activities` by their `code` — those two have no `id` of their own at all. A client that read `{id}` sent the record's own `id` field and got `404 SMART_PROCESS_NOT_FOUND` naming that same value as an `entityTypeId` — while the published documentation already said `:entityTypeId` and `:code`.

**After**

`GET /v1/guide` shows `/v1/smart-processes/:entityTypeId`, `/v1/telephony-lines/:number`, `/v1/bizproc-robots/:code` and `/v1/bizproc-activities/:code`; every other entity keeps `/:id`. In OpenAPI the parameter name stays `id`: a path parameter's name has to match the placeholder in the path template, and the address itself did not change — instead the parameter description now names the real identifier. Where an entity declares no `id` field of its own (the telephony lines and both bizproc registries) the description says exactly that, rather than sending the caller to compare against a field that does not exist. The description of the `id` field on `smart-processes` was sharpened too.

**Impact on integrators**

Nothing to change: routes and response codes are unchanged, only descriptions are. If you were putting the record's internal `id` into the `smart-processes` path, the 404 now has an explanation — use `entityTypeId`; for business-process robots and activities, use `code`.

### FIX-0804-7: telephony lines: `name` declared nullable, `serverName` marked as never returned

**Before**

`GET /v1/telephony-lines/fields` declared `name` as a plain string, although a line created without a name comes back as `null` — a model generated from the reference broke on the first such value. The `serverName` field looked like an ordinary readable field even though Bitrix24 neither stores nor ever returns it.

**After**

`name` now carries `nullable: true` — in `GET /v1/telephony-lines/fields`, in `GET /v1/guide` and in the OpenAPI schema (there as the `type: ["string","null"]` form). `serverName` now carries `notReturned: true` in the same places, plus an `x-notReturned` annotation and a sentence in its OpenAPI description. The field deliberately stays in the reference: a write to it is still rejected with `400 READONLY_FIELD`, and a client must be able to look the field up and read why.

**Impact on integrators**

Nothing to change — both flags are additive. If you generate types from the reference, `name` becomes `string | null` and `serverName` can be dropped from the read model.

### FIX-0804-8: workgroups: `limit > 50` and row-exact offset now work

**Before**

`GET /v1/workgroups?limit=500` returned the first 50 records no matter how many groups were available, and `meta.hasMore` did not help read the rest. The cause: this entity's list method is not a `.list` one but `sonet_group.get`, and the "this is a list" flag was not declared for it, so neither `limit` nor auto-pagination ever reached Bitrix24. The offset was floored to a page boundary at the same time: `offset=30` returned records starting from the first one, not the thirty-first.

**After**

`limit` reaches Bitrix24, and for `limit > 50` the platform reads as many pages as needed — the way it has long worked for users, departments and storages. The offset is row-exact: `offset=30` starts the window at the 31st record. The same behaviour applies to `POST /v1/workgroups/search` and to a list sub-call of `POST /v1/batch`.

**Impact on integrators**

If you paged workgroups by hand and compensated for the floored offset on your side (for example by dropping the leading records of a page), remove that compensation — otherwise records will be skipped twice. For deep paging a cursor is more reliable: filter `{">id": lastId}` with sorting by `id`.

### FIX-0804-9: deleting an application now completes its removal from the account

**Before**

[DELETE /v1/apps/:id](/docs/apps/delete) removed the application from the Bitrix24
account in a single attempt. If the account was unreachable, the rights had been
revoked, or the application author had no developer key, the attempt was silently lost:
the application was deleted on our side but stayed installed on the account, with its
menu entry still in place. Widgets were unbound only for applications published in the
catalog, and only on cloud accounts.

**After**

The outcome of the attempt is stored, and an unfinished removal is retried in the
background with a growing delay until the account confirms it. Widgets are unbound for
any application, whether or not it was published in the catalog.

**Impact on integrators**

The endpoint response is unchanged — still `204` right after the deletion on our side.
What changed is the result: the application entry on the account now disappears in the
cases where it used to stay forever.

### NEW-0804-10: Gateway timeout on exec now carries a recovery hint

A `GATEWAY_TIMEOUT` failure of [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) now carries a `hint` object with `reason`, `recovery` and `recoveryAction` fields — the same way `EXEC_TIMEOUT` and an agent-side `EXEC_BUSY` already do. The hint states the essential part: no exit status came back, so the outcome of the command is unknown and it may still be running on the server. Re-running it blindly can start a second copy alongside the first, so establish the real state first — read the logs or issue a short read-only command. For work that legitimately outlives the time limit the hint points at a detached background job.

The `code` and `message` fields are unchanged — the hint is additive and existing calls keep working. It arrives in both response modes: in the JSON envelope and as an SSE `error` event.

### FIX-0804-11: model-unavailable refusal is now 429 with a wait hint, not 502

**Before**

When access to the models was temporarily closed after a run of failing calls, `POST /v1/chat/completions` and `POST /v1/embeddings` answered **502** with code `AI_PROVIDER_UNAVAILABLE`, and `error.message` carried an internal service string instead of an explanation. There was no `Retry-After` header, so a client had nothing to wait on — the typical library reaction to a 5xx is an immediate retry, which prolonged the outage.

**After**

The same refusal arrives as **429**, `error.type: "rate_limit_exceeded"`, `error.code: "ai_provider_cooldown"` (the streaming frame uses the same name in upper case, as the rest of this code family does), with a **`Retry-After`** header in seconds; in a streaming response the same value arrives as a `retryAfter` field inside the terminal error frame. The value is the remainder of the wait window, never below one second. `error.message` no longer contains internal strings or addresses. The refusal is temporary: wait out `Retry-After` and repeat the same request.

### FIX-0804-12: a multipart archive deploy now keeps the source version even when the deploy fails

**Before**

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) with a `multipart/form-data` body saved the archive to source storage only after a successful deploy. When the deploy failed, no version was created at all — nothing to inspect, and a retry meant sending the same bytes again.

**After**

The archive is placed into source storage before the deploy starts, and the deploy proceeds from a link to that version. The version stays in history either way: `deployStatus: "success"` on success, `"failed"` on failure. The `data.source` block is unchanged — `autoSaved`, `savedVersionId`, `sha256` and `newVersion` are filled in as before, and re-sending identical bytes still deduplicates instead of minting a new version.

A new refusal code `SOURCE_DEPOT_UNAVAILABLE` (502) was added: source storage did not accept the archive, the deploy never started, and the same request can be retried as is. Previously such a failure surfaced as `VALIDATION_ERROR` (400), i.e. it looked like a bad request.

**Impact on integrators**

No action required. The version list ([GET /v1/infra/servers/:id/sources](/docs/source-storage)) of clients that deploy via multipart may now contain versions from failed deploys — those carry `deployStatus: "failed"`.

### NEW-0804-13: address fields come with human-readable labels and descriptions

Previously [GET /v1/addresses/fields](/docs/entities/addresses/fields) returned the Bitrix24 field name in `title` for six of the fourteen fields — `TYPE_ID`, `ENTITY_TYPE_ID`, `ENTITY_ID`, `COUNTRY_CODE`, `ANCHOR_TYPE_ID`, `ANCHOR_ID`. Such a label cannot be shown to a user, and the meaning of the codes had to be looked up in the documentation.

Now those six fields carry a label in the account language under `title`, and the fields that have something to add to the label gained a `description` key with the purpose of the field and the meaning of its codes: address types (all twelve, 1 through 12 — which of them are available depends on the account country zone) and owner types (1 — lead, 3 — contact, 4 — company, 8 — requisite). The `countryCode` description no longer promises a format: Bitrix24's own REST documentation marks that field as unused and kept for backward compatibility. Code 1 additionally carries the label the English Bitrix24 interface uses for it — Street address: Bitrix24 itself names that type differently in its Russian and English versions, and without the note the dictionary would not match the label the user sees in the interface.

Labels that Bitrix24 supplies itself are unchanged. Alongside `title`, the same label now also arrives under `label` — the key every other entity uses — so labels can be read one way on any entity. The `description` and `label` keys are added to the field description and the previous keys stay in place, so no action is needed.

The address type codes were also corrected in the documentation for creating, reading, updating and deleting an address: the values listed there were wrong, and code 13 does not exist at all — the set of types ends at 12, and which of them are available depends on the account country zone.

### NEW-0804-14: product fields come with labels, descriptions and a description-format dictionary

[GET /v1/products/fields](/docs/entities/products/fields) described all twenty-one product fields with nothing but a type and a read-only flag — no label, no description. Such a response gave no way to tell that `measure` is a measurement unit and `vatId` is a VAT rate.

Now every one of the twenty-one fields carries a `label` in the account language, and the fields that have something to add to the label also carry a `description`: where to get the list of allowed values (`GET /v1/currencies`, `GET /v1/product-sections`, `GET /v1/catalogs`, `GET /v1/users`), how sorting works and what sets the description format. The `descriptionType` field gained an `enum` dictionary with the values `text` and `html`.

The keys are added to the field description and the existing `type` and `readonly` are unchanged, so no action is needed. Catalog properties `PROPERTY_<N>` still arrive with the label from the account settings.

### NEW-0804-15: all 45 quote fields come with a label and a description

[GET /v1/quotes/fields](/docs/entities/quotes/fields) returned a label for thirty of the forty-five fields. The remaining fifteen were exactly the base ones — `id`, `title`, `dealId`, `contactId`, `companyId`, `amount`, `currency`, `assignedById`, `createdBy`, `comments`, `isManualOpportunity`, `beginDate`, `closeDate`, `createdTime`, `updatedTime` — described by nothing but a type and a read-only flag. Not a single field carried a `description`.

Now every one of the forty-five fields has a label, and each gained a description: what the field is for, where to get the list of allowed values (`GET /v1/currencies`, `GET /v1/users`, `GET /v1/deals` and others), write-time behaviour. The naming mismatches that are easy to get wrong are stated explicitly: the amount is named `opportunity` in Bitrix24, the currency is `currencyId`, and the start and close dates are `begindate` and `closedate`, all lowercase.

The `stageId` field deliberately has no value dictionary: the set of stages is configured in the account, so its description points at the `GET /v1/statuses?filter[entityId]=QUOTE_STATUS` directory — a static dictionary would go stale.

The keys are added to the field description and the existing `type` and `readonly` are unchanged, so no action is needed.

### NEW-0804-16: catalog product fields come with labels and descriptions

[GET /v1/catalog-products/fields](/docs/entities/catalog-products/fields) described all forty-two catalog product fields with nothing but a type and service flags — no label, no description. Such a response gave no way to tell how `purchasingCurrency` differs from the price currency, or `quantityTrace` from `canBuyZero`.

Now every one of the forty-two fields carries a `label` in the account language, and thirty-eight of them also carry a `description`. The descriptions state what previously had to be found out by trial: `iblockId` is set on create only and does not allow moving a product between catalogs; `iblockSection` is accepted on write only, while on read the primary section arrives as the scalar `iblockSectionId`; `available` and `bundle` are computed by Bitrix24; `recurSchemeLength`, `recurSchemeType` and `trialPriceId` work only in on-premise Bitrix24 for content sales. Where a value comes from a directory, the endpoint is named — `GET /v1/catalogs`, `GET /v1/catalog-sections`, `GET /v1/currencies`, `GET /v1/users`.

For `previewTextType` and `detailTextType` the value set arrives as a machine-readable `enum` dictionary (`text` and `html`) — the same shape the product description format uses, instead of prose inside the description.

The keys are added to the field description and the existing `type`, `readonly`, `createOnly` and `nullable` are unchanged, so no action is needed.

### FIX-0804-17: requisite preset field schema declares inShortList as boolean

**Before**

[GET /v1/requisite-presets/:presetId/fields/schema](/docs/entities/requisite-presets/preset-fields/schema) described the `inShortList` field with the `char` type, while reading rows of the same preset returns `true`/`false` and writing accepts `true`/`false`. The schema contradicted the data it describes, and a client relying on the declared type prepared to parse a single-character string.

**After**

In the same response `inShortList.type` arrives as `boolean`. The other keys of the field description (`isRequired`, `isReadOnly`, `title` and the rest) are unchanged, and so are the types of the other fields.

**Impact on integrators**

No action needed: the data was already boolean. A check comparing `inShortList.type` against the string `char` will stop matching — compare against `boolean` instead.

### NEW-0804-18: downloading call recordings and timeline attachments

Two endpoints were added for CRM files that Disk file download could not reach.

[GET /v1/activities/:activityId/files/:fileId/download](/docs/entities/activities/file-download) returns an activity file, including a call recording. Doing this through the API was previously impossible: an activity file is not a Disk object, its identifier lives in a separate space that overlaps Disk identifiers, and the link in the activity response arrives with an empty authorization parameter — requesting it returns the sign-in page with code `200`. The endpoint adds the authorization itself, verifies that the file really belongs to the named activity, and returns a byte stream.

[GET /v1/timelines/:commentId/files/:fileRef/download](/docs/entities/timelines/file-download) returns a timeline comment attachment. `fileRef` accepts either identifier: the attachment ID the account interface shows, and the Disk object ID — the object key in the `files` field of the comment response. The first computes access through the comment itself, so it reaches attachments that Disk file download refuses to serve; the second goes through personal Disk permissions. The comment itself is read first, then the Disk object ID, and the attachment ID only when the comment does not list that reference; you do not have to state which one you pass.

Both endpoints require the `crm` scope and never return the download address: it contains an authorization code, so only the content leaves. A file whose membership in the named activity or comment is not confirmed gets `404` — including an attachment that hangs on a different kind of record, a task with the same number for instance — without that check the endpoint would allow enumerating the account's files, because Bitrix24 itself answers such a request with a page under code `200` rather than a refusal.

The address check extends to redirects: the endpoint walks them itself, checking every hop, bounds the chain in length and in time, and does not follow a redirect to an internal address — such an answer becomes `502`. The same applies to [Disk file download](/docs/entities/files/download), which uses the same wrapper.

A `404` on the attachment download means the reference itself is wrong. A temporary cause — a Bitrix24 request limit, an unavailable account, the repeated-error guard tripping — comes back as itself: `429` or `502`/`503` with a `Retry-After` header. The practical difference: retrying a `404` is pointless, whereas a `429`/`5xx` should be retried with a delay. The `Retry-After` value is computed for the Bitrix24 call that actually hit the limit.

The contract of [Disk file download](/docs/entities/files/download) is unchanged: same parameters, same responses. Through the shared wrapper it inherited only the address and redirect check described above.

### FIX-0804-19: a bot message without text is refused with a clear error instead of a false success

**Before**

[POST /v1/bots/:botId/messages](/docs/bots/messages/send) with the text under an unrecognized key — for example `{"dialogId": "…", "text": "hi"}` — reached Bitrix24 with no content, and Bitrix24 answered `422` with the code `EMPTY_MESSAGE` and the text "Message can't be empty". That response gave no way to tell the field name was the problem: the text had been passed.

Update behaved worse. [PATCH /v1/bots/:botId/messages/:messageId](/docs/bots/messages/update) answered `200 {"result": true}` in the same situation while the message text stayed unchanged — a false success after which an integrator considered the edit applied.

**After**

Both requests check for content before calling Bitrix24 and answer `400` with the code `MESSAGE_REQUIRED` when there is none. The error text lists the unrecognized body keys and states that the message text belongs in the `message` field. Neither an empty `attach` array nor an empty `message` string counts as content; an `attach` block without text does, and so does a number (`0` is the text "0").

The `fields` wrapper is how a body is passed through in Bitrix24's own shape, and "a wrapper was supplied" is now understood the same way at every processing step. A value that is not a wrapper (`false`, `0`, an empty array) is not treated as one: `{"dialogId": "…", "fields": false}` gets the same `400` with the code `MESSAGE_REQUIRED`, and `{"message": "hi", "fields": []}` sends the text instead of losing it.

This is the same code and the same wording as the sibling [POST /v1/chats/:dialogId/messages](/docs/chats/messages/send): one contract for the same mistake across two related endpoints.

**Impact on integrators**

A request with the text in the `message` field works as before. A request that used to get `422 EMPTY_MESSAGE` now gets `400 MESSAGE_REQUIRED` telling it what to fix. The `text` field does not become a synonym for `message`: on read the message content really is called `text`, but silently accepting both names would split the contract with the chats endpoint.
