# API changes: July 9, 2026

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

### FIX-0709-1: transient DB overload now returns 503 with Retry-After instead of 500

**Before**

In a rare form of transient DB overload (connection exhaustion), some requests (including `POST /v1/infra/servers`) returned `500`.

**After**

Such requests return `503` with code `POOL_EXHAUSTED` and a `Retry-After` header. The error is transient — retry the request with backoff.

**Impact on integrators**

Clients that retry on 5xx must now handle `503` and honor `Retry-After`. `POST /v1/infra/servers` is non-idempotent — a retry can create a duplicate server, so retry with backoff rather than immediately.

### FIX-0709-2: placements/bind reports the Bitrix24 plan requirement clearly

**Before**

Binding a placement with an app key on an account whose Bitrix24 plan does not permit developer-key REST calls made Bitrix24 deny access, and `POST /v1/placements/bind` returned an opaque `502 BITRIX_UNAVAILABLE` with no cause. The plan is checked on the developer-key path, but `GET /v1/me` gave no advance signal about this prerequisite.

**After**

The denial is now classified: `POST /v1/placements/bind` returns `403` with code `INT_TARIFF_REQUIRED` and a readable `userMessage` — the developer-key path needs a commercial Bitrix24 plan on the account. An app key's `GET /v1/me` gains a `placements.bindPrerequisite` block that describes the Bitrix24-side prerequisite up front and lists the error codes. Other bind failures (unknown clientId, stale embedding) still return `502 BITRIX_UNAVAILABLE`.

**Impact on integrators**

No action required, successful calls are unchanged. If you handled `502 BITRIX_UNAVAILABLE` on bind, also handle `403 INT_TARIFF_REQUIRED` and prompt the user to upgrade the account to a commercial Bitrix24 plan.

### FIX-0709-3: bindPrerequisite in GET /v1/me now reflects the Bitrix24 plan

**Before**

The `placements.bindPrerequisite` block on an app key described the bind prerequisite identically for every account: `subscriptionRequired: true`, a remedy that does not apply to an account whose access is granted by the Bitrix24 plan, and an `errorCodes` list carrying codes such an account never receives. On it the bind denial arrives as `INT_TARIFF_REQUIRED`.

**After**

The block now depends on the account. On an account whose access is granted by the Bitrix24 plan, `subscriptionRequired` is `false`, the `note` describes the commercial-plan requirement, and `errorCodes` carries `INT_TARIFF_REQUIRED` and `BITRIX_UNAVAILABLE` — only the codes that account can actually receive.

**Impact on integrators**

Successful calls are unchanged. If you read `errorCodes` from `bindPrerequisite` as an exhaustive list, note that it is now narrowed to the codes reachable on the calling account. The codes themselves and the behavior of `POST /v1/placements/bind` are unchanged.

### BC-0709-4: CRM, task and landing fields aligned with the real Bitrix24 contract

> Old format supported until: 09.01.2027

**Before**

`GET /v1/deal-categories` returned `isLocked` as the string `"Y"`/`"N"`, and `GET /v1/payments` returned `paySystemIsCash` as the string `"Y"`/`"N"`. `GET /v1/leads` exposed the internal `searchContent` field (Bitrix24's full-text search index) in every response. The payment fields `paySystemXmlId`, `dateMarked`, `dateResponsibleId` passed through as-is, with no date normalization.

**After**

`isLocked` (pipelines) and `paySystemIsCash` (payments) are now boolean (`true`/`false`). `searchContent` is no longer returned by `GET /v1/leads`. Payments now declare `paySystemXmlId` (string), `dateMarked` and `dateResponsibleId` (datetimes normalized to a single ISO form with a `Z` suffix). Tasks gained `changedBy`/`closedBy`/`statusChangedBy` (read-only — writing them returns `400 READONLY_FIELD`). `GET /v1/currencies/fields` now returns human-readable `label` values and a `lang` field; `GET /v1/deal-categories/fields` returns `label` values; `GET /v1/sites/fields` sets a `nullable` flag on fields that may come back empty.

**What integrators should do**

Read `isLocked` and `paySystemIsCash` as boolean instead of comparing to the string `"Y"`. If your code relied on the leads `searchContent` field, stop: it was an internal, undocumented field.

### FIX-0709-5: creating an entity with an empty body is rejected with an explicit error

**Before**

Creating an entity with an empty body (or no body at all but a `Content-Type: application/json` header) reached Bitrix24 and silently created an entity with default values — including deals, leads, contacts, companies, invoices and smart processes. A retry or a bodyless client thus spawned junk records in CRM. This affected all three create surfaces: single `POST /v1/{entity}`, per-entity `POST /v1/{entity}/batch` and global `POST /v1/batch`.

**After**

Such a request returns `400 EMPTY_CREATE_BODY` (a per-item error in batch calls) before Bitrix24 is called, on all three surfaces. A meaningful create always carries at least one field — send the fields you need in the request body. Entities that already validate their required fields behave as before.

### FIX-0709-6: releasing a server lock accepts an empty JSON body

**Before**

[DELETE /v1/infra/servers/:id/lock](/docs/infra) with a `Content-Type: application/json` header and an empty body returned `400` (empty JSON body). Releasing a stuck lock required sending an explicit `{}`, and a client unaware of that hit a dead end.

**After**

An empty body with that header is accepted as `{}`; a bodyless request works and releases the lock. An explicit `{}` still works too.

### FIX-0709-7: Bitrix24 events now wake a sleeping galaxy app

**Before**

A Bitrix24 event sent to a sleeping galaxy app's subscription did not wake it — delivery retried and
was lost after the attempts ran out.

**After**

The platform wakes a sleeping galaxy app on event delivery and delivers the event once it is up —
same as for a standalone server.

### NEW-0709-8: camelCase keys inside communications when creating an activity

[POST /v1/activities](/docs/entities/activities/create) now accepts the nested keys of `communications` items in camelCase (`type`, `value`, `entityTypeId`, `entityId`) — consistent with the rest of the API. Previously the nested keys were accepted only in Bitrix24 UPPER case (`TYPE`, `VALUE`, `ENTITY_TYPE_ID`, `ENTITY_ID`), and the camelCase form was silently dropped — `communications: [{ "type": …, "value": … }]` returned `422` "COMMUNICATIONS is not defined or invalid", while `[{ "TYPE": …, "VALUE": … }]` created the activity. UPPER case still works; if both forms of the same key appear in one object, the UPPER-case one wins.

### FIX-0709-9: unpublishing removes the tab across all of the app's handlers

**Before**

[POST /v1/apps/:id/unpublish](/docs/apps/unpublish) unbound the placement only by the handler matching the assumed platform address. If the tab was bound to the app's own technical address, Bitrix24 did not find it and did not remove it — the tab stayed stuck in the CRM card and could no longer be removed via the API.

**After**

Unpublishing now removes the placement across all of the app's handlers, including one bound to the server's technical address — the orphaned tab disappears.

### NEW-0709-10: Key self-description in GET /v1/guide

The `GET /v1/guide` response now carries a `data.keysAuth` block. It describes the two self-description endpoints — `GET /v1/me` and `GET /v1/guide` — and links to their documentation: the response contract of each, the key access mode, and the overview of key types.

Both endpoints work with the `X-Api-Key` header alone, no session token is required.

The field is additive: existing clients are unaffected.

**Affected endpoints:** [GET /v1/guide](/docs/keys-auth/guide)

### FIX-0709-11: GET /v1/{entity}/:id now honours ?select=

The `?select=` field projection was ignored on single-record reads by id: the response always came back with every field, even though `/v1/me` states `?select=` works "on list, get by id, and POST /search". Get-by-id now projects the response the same way list and search do, bringing the behaviour in line with what was documented.

**Before**

`GET /v1/leads/42?select=id,title` returned the full record (all fields).

**After**

`GET /v1/leads/42?select=id,title` returns only `id` and `title`. The comma (`?select=id,title`), array (`?select[]=id&select[]=title`) and indexed (`?select[0]=id&select[1]=title`) forms are all accepted; `id` is always included in the response. An unknown field name is silently skipped — it is not an error. When `?select=` and `?include=` are combined, the included relation is preserved in the response. The indexed form (`?select[0]=…`) previously returned 500 on the list endpoint `GET /v1/{entity}` too — it now projects correctly there as well.

### FIX-0709-12: /fields for orders, basket items, requisite presets and document templates now matches the real B24 contract

**Before**

`GET /:entity/fields` (and the OpenAPI schema generated from it) declared fields that Bitrix24 never returns: `provider` on document templates; `reserved`, `sumPaid`, `dateBill`, `datePayBefore`, `datePaid`, `empPaidId`, `userEmail`, `userName` at the order top level; `module`, `fUserId`, `lid`, `dateRefresh`, `subscribe`, `reserved`, `reserveQuantity` on basket items; `originatorId` on requisite presets. Filtering and sorting by those fields silently did nothing. Meanwhile real fields were left undeclared: `requisiteLink` on the order, `type`/`properties`/`reservations` on the basket item. Requisite presets accepted `countryId`/`entityTypeId` on update, where Bitrix24 silently ignores them.

**After**

The non-existent fields are removed from `/fields` and OpenAPI. The real fields are declared: on the order — `requisiteLink` (object `requisiteId`/`bankDetailId`/`mcRequisiteId`/`mcBankDetailId`, read-only); on the basket item — `type`, `properties`, `reservations` (read-only). On requisite presets, `countryId` and `entityTypeId` are marked create-only: an update returns `400 READONLY_FIELD` instead of a silent no-op.

**Impact on integrators**

list/get responses are unchanged — the removed fields were never returned. If a request filtered or sorted by a removed field it now returns `400` — use the real fields from `/fields` (for example, an order's payment dates and sums live inside the `payments` array, not at the top level). Updating `countryId`/`entityTypeId` on a requisite preset is now explicitly rejected — set those fields on create only.

**Affected endpoints:** [GET /v1/orders/fields](/docs/entities/orders/fields), [GET /v1/basket-items/fields](/docs/entities/basket-items/fields), [GET /v1/requisite-presets/fields](/docs/entities/requisite-presets/fields), [GET /v1/doc-templates/fields](/docs/entities/doc-templates/fields)

### NEW-0709-13: GET /:entity/fields returns human-readable labels and descriptions for deals, leads, invoices, activities, statuses and timeline comments

The response of `GET /v1/deals/fields`, `/v1/leads/fields`, `/v1/invoices/fields`, `/v1/activities/fields`, `/v1/statuses/fields` and `/v1/timelines/fields` now carries a human-readable `label` and `description` for every field, returned in English. Fields with magic-number codes gained `enum` dictionaries: deals — `stageSemanticId` (P — in progress, S — success, F — failure); activities — `typeId`, `direction`, `priority`, `status`, `notifyType` and `descriptionType`. Existing calls keep working unchanged — these are additive metadata fields, the response shape does not change.

### FIX-0709-14: POST /v1/batch — unified sub-call error shape and totals only for list/search

**Before**

A Bitrix24 error inside a successful 200 [POST /v1/batch](/docs/batch) response (for example, a `get` of a missing element) landed in `data.errors[<id>]` in Bitrix24's native `{ error, error_description }` form — not the V1 envelope `{ code, message }` used by validation errors and every other API response. Meanwhile `data.totals[<id>]` was populated for any action, including `get`/`create`/`update`/`delete`, where a lone number next to a single record is meaningless.

**After**

A sub-call error is mapped to `{ code, message }` (`error` → `code`, `error_description` → `message`), consistent with all other errors. `data.totals[<id>]` is populated only for `list` and `search` actions, where a match count actually has meaning.

### FIX-0709-15: GET /v1/openline-configs — empty-value normalization in the response

**Before**

[GET /v1/openline-configs](/docs/openlines/config/list) and [GET /v1/openline-configs/:id](/docs/openlines/config/get) returned service fields in shapes awkward for clients: `KPI_FIRST_ANSWER_LIST`, `KPI_FURTHER_ANSWER_LIST`, `DEFAULT_OPERATOR_DATA` came back as `null` (client `.map`/`.length` broke on them); `AUTO_CLOSE_TEXT` for an unset value came back as `""` in the card but `null` in the list; `WORKTIME_HOLIDAYS`/`WORKTIME_DAYOFF` for an empty set came back as `[""]` (an array with one empty string).

**After**

Lists are normalized: `null` → `[]`. `AUTO_CLOSE_TEXT` converges on a single `null` for an unset value in both the list and the card. `WORKTIME_HOLIDAYS`/`WORKTIME_DAYOFF` return `[]` for an empty set. The list and the card now return the same shape for these fields.

### FIX-0709-16: GET /v1/users/fields returns the possible values (items) for enumeration user-fields

**Before**

A custom enumeration user-field (`UF_USR_*` of the "list" type) came back from `GET /v1/users/fields` as `{ "type": "string", "label": "…" }` — with no list of possible values. Reason: for UF fields the `user.fields` method returns only a label, not the type or the variants, so an enumeration was indistinguishable from a string.

**After**

Such a field now carries its real type and the list of variants: `{ "type": "enumeration", "label": "…", "items": [ { "ID": "…", "VALUE": "…", "DEF": "…", "XML_ID": "…" }, … ] }`. The values are read from `user.userfield.list` — the key needs the `user.userfield` scope for this; without it the field is still returned with its label but without `items` (graceful degradation). Other UF fields (`money`, `date`, …) likewise now show their real type instead of `string`.

### FIX-0709-17: the empty-queue hint now appears after sustained emptiness

**Before**

[GET /v1/bots/:botId/events](/docs/bots/events/polling) incremented the empty-response counter on exactly every request, and the `hint` field appeared strictly after the fifth consecutive empty response. The number in the hint text matched the number of requests made.

**After**

The empty-response counter is updated periodically rather than on every request, so `hint` appears after the queue stays empty for a sustained period — at the recommended 2–5 second polling interval, after roughly a couple of minutes of continuously empty polling. The number N in the text reflects the count of observed empty periods, not the exact number of requests made. The "an event was delivered → counter and hint reset" rule is unchanged.

**Impact on integrators**

No code change is needed. If you relied on `hint` appearing strictly on the fifth request or read N as an exact request count — use `persisted` and the presence of events as the primary signal, and treat `hint` as a diagnostic hint.
