# API changes: July 31, 2026

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

### BC-0731-1: source uploads require Content-Length

> Old format supported until: 30.01.2027

The source-upload endpoints — [POST /v1/apps/{id}/sources](/docs/source-storage) and [POST /v1/infra/servers/{id}/sources](/docs/source-storage) — now accept a body only with the `Content-Length` header. A request without it (chunked transfer, `Transfer-Encoding: chunked`) gets `411` with code `MISSING_CONTENT_LENGTH`.

The reason: the archive body is no longer assembled in memory in full — it is forwarded to storage as it arrives, and that requires the length to be known up front.

The vast majority of clients already send the header: `curl --data-binary`, `fetch` with a buffer body and any HTTP client posting a whole file all set it. Only clients that deliberately stream a body of unknown length are affected.

Re-saving an identical archive also costs a little more now: the content match is determined after the body has been received, so the response comes slightly later and the volume counts toward storage operations. The outcome is unchanged — `deduplicated: true` and the same version.

**What integrators should do**

Send the archive as a whole (`--data-binary @file` in curl, a buffer or a file as the request body) rather than as a stream of unknown length. If your client streams the body itself, compute the size up front and set `Content-Length`.

### FIX-0731-2: tariff name in GET /v1/me comes from the platform edition catalogue, in English

**Before**

The `data.tariff.name` field came from a stored tariff snapshot: the licence name as the account reported it, or — when the account reported none — a value from an internal dictionary that had no language awareness. On some accounts it was not a tariff name at all: the response carried either a Russian-language plan name on a non-Russian account, or the licence code itself, such as `pro100`, presented as a human-readable name.

**After**

The name is resolved at response time. When the tariff code is known to the platform edition catalogue, the field comes from there in English, so an account now receives `Demo period`; the name reported by the account is not used in that case, so the label can also change where nothing was wrong. When the code is not in the catalogue, the field still carries the name reported by the account, verbatim and in its own language. Only when there is no name at all, or the only candidate is the licence code itself, is the field `null` — the licence code is never substituted for a name. The field type is unchanged: string or `null`, and the code itself is still available in `data.tariff.code`.

Three edition labels were also renamed to fuller forms: the demo plan is now `Demo period`, the legacy free plan is now `Project — legacy free`, and the partner licence is now `NFR — partner licence`. Their previous values were the short Russian-language forms, so a client comparing this field by string should re-check the comparison.

### FIX-0731-3: server user search returns active employees only

**Before**

[GET /v1/infra/servers/:id/b24-users](/docs/infra/access/b24-users) could return inactive users and users who are not employees.

**After**

The endpoint returns only users confirmed as active Bitrix24 employees. No integration changes are required.

### NEW-0731-4: an app now decides for itself whether the placement iframe auto-height is on

Apps gained an optional `placementResizeEnabled` field (defaults to `false`). It controls whether the platform serves a wrapper page on a placement open that fits the iframe height to your app's content.

The field is returned by [GET /v1/apps](/docs/apps/list) and [GET /v1/apps/:id](/docs/apps/get) and accepted by [PATCH /v1/apps/:id](/docs/apps/update). Existing calls keep working unchanged: every existing app carries `false`, so the placement open behaves exactly as before.

**Turn it on only together with a change on the app side.** The wrapper opens the app in a nested iframe on the platform origin, so the app gains a new ancestor origin. If your app sends its own `Content-Security-Policy` header with a `frame-ancestors` directive, add the platform origin to it — otherwise the browser refuses to open the app. Apps without their own `frame-ancestors` directive need no change.

If your app already had the auto-height working, turn the field on to keep the previous behaviour. Note that the field is a necessary condition, not the only one: the capability itself is rolled out account by account.

To report its height, the app posts a `{ type: 'vibe:resize', height }` message to the parent window — the contract is described in [App inside Bitrix24](/docs/infra/app-runtime).

### FIX-0731-5: an empty body with the JSON header is no longer rejected on the infrastructure routes

**Before**

An operation that needs no body answered 400 with the `FST_ERR_CTP_EMPTY_JSON_BODY` code when a client sent the `Content-Type: application/json` header with no body. Clients that attach this header to every request do that — axios and PowerShell `Invoke-RestMethod`, for example. The refusal happened while parsing the body, that is, before the key was checked, so the response gave no way to tell what was wrong with access. It affected [`DELETE /v1/infra/servers/:id/access-tokens/:tokenId`](/docs/infra/access-tokens), [`POST /v1/infra/servers/:id/wake`](/docs/infra/lifecycle/wake) and the rest of the body-less server operations.

**After**

An empty body is accepted as `{}`, and the operation answers on the merits — 401 on a wrong key, 404 on a server that does not exist, 200 on success. The former workaround of passing an explicit `{}` body keeps working. An unparsable body on these routes now returns the `INVALID_JSON_BODY` code instead of `FST_ERR_CTP_INVALID_JSON_BODY` — the same code the neighbouring operations of the same server already returned, including `deploy`, `exec` and `lock`.
