# API changes: July 28, 2026

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

### NEW-0728-1: an erased author's app no longer issues new user tokens

Once an application's author has erased their personal data, the application stops issuing tokens to new users. Such a request previously reached Bitrix24 and created a working token together with the new user's name and email, even though the author is already gone from the system.

The return from [GET /v1/oauth/callback](/docs/keys-auth/oauth) then arrives at your `redirect_uri` with `?error=app_unavailable` — the same shape `token_exchange_failed`, `invalid_domain`, and `profile_fetch_failed` already use. [POST /v1/oauth/placement-session](/docs/keys-auth/oauth) answers `403` with the `APP_UNAVAILABLE` code — so does the placement handler Bitrix24 opens the application widget through (it previously answered `401` with the generic `USER_AUTH_REQUIRED`, which read as a user-authorization problem).

Tokens and sessions already issued for that application are not renewed. Previously working calls are unaffected: while the author is active, both endpoints behave exactly as before.

### NEW-0728-2: stuck-lock release is now cross-replica; the DELETE /lock response carries broadcast and localLock

[DELETE /v1/infra/servers/:id/lock](/docs/infra/deploy/lock) now broadcasts the release to **all** platform replicas, so it releases a stuck lock even when it is held on a different replica (a common case under horizontal scaling). The response gains `broadcast` (the release was broadcast fleet-wide, best-effort) and `localLock` (whether the lock was held on this replica). The `released` field now describes only the current replica and is **not proof of a fleet-wide release** — when the stuck lock is on another replica, `released` can be `false` while the lock really was released; do not poll the endpoint until `released: true`, retry the operation instead. Existing calls keep working unchanged (the fields are additive). Additionally, a stuck `exec` lock is now guaranteed to be released by a server-side auto-sweep shortly after its TTL expires.

The [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) response on `502 EXEC_BUSY` for a galaxy app now carries an `error.hint` with an honest recovery path (the shared host exec channel; escalation to the platform team — `DELETE /lock` does not help there, as the agent mutex is the blocker). The [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) response on `409 GALAXY_APP_BUSY` gains `error.hint`, `retryable: true`, `retryAfter`, and a `Retry-After` header.

### NEW-0728-3: pointers to Open Channels, task checklists and app blueprints in the self-description responses

The [GET /v1/guide](/docs/keys-auth/guide) response gained the `data.appBlueprints` pointer — a link to the ready-made app spec documentation and the condition behind the `403 BLUEPRINTS_DISABLED` response.

For a key with the `imopenlines` scope the response also carries the `data.openLines` block: a section overview, links to all seven documentation pages and the split between two endpoint groups. Line configuration and the operator actions are available on every Bitrix24 account. Dashboard statistics answer `422 METHOD_NOT_YET_AVAILABLE` until the Bitrix24 update reaches the account, and `403 B24_TARIFF_RESTRICTION` without the statistics-access right.

In the [GET /v1/me](/docs/keys-auth/me) response the `api._rules` block gained three new pointers — to [task checklists](/docs/entities/tasks/checklist), [Open Channels](/docs/openlines) and [app blueprints](/docs/app-blueprints).

The fields are additive, existing clients are unaffected. The endpoints themselves did not change.

### FIX-0728-4: galaxy deploy now recovers a dropped host tunnel

**Before**

Deploying a galaxy app onto a host whose tunnel had silently dropped under build load (including a phantom-CONNECTED host — the flag was stale while the tunnel was already dead) looped on `GALAXY_HOST_UNREACHABLE` / `GALAXY_DEPLOY_INTERRUPTED`: the platform did not repair the tunnel itself, and the client's retries kept hitting the same dead tunnel.

**After**

Such a drop on the deploy path now triggers a background repair of the host tunnel, so an honest retry lands on a recovered tunnel and the deploy completes. The error codes and their retryable semantics are unchanged — only the behavior improves (self-healing).

### BC-0728-5: unified 404 envelope for nonexistent /v1 routes

> Old format supported until: 28.07.2026

> The previous body shape is not served — there is no transition period with a dual format, the change takes effect on the publication date.

**Before**

A request to a nonexistent path or an unsupported HTTP verb under `/v1/` answered with the web-server body outside the unified API envelope:

```json
{
  "message": "Route GET:/v1/dealz not found",
  "error": "Not Found",
  "statusCode": 404
}
```

**After**

The same request answers in the unified V1 envelope with the new `ROUTE_NOT_FOUND` code. The HTTP status is unchanged — `404`:

```json
{
  "success": false,
  "error": {
    "code": "ROUTE_NOT_FOUND",
    "message": "Route GET:/v1/dealz not found. Check GET /v1/guide for available endpoints and verbs."
  }
}
```

`ROUTE_NOT_FOUND` means "no such route or verb exists" — check the path against the list in `GET /v1/guide`. Do not confuse it with `ENTITY_NOT_FOUND` and the domain codes of the `*_NOT_FOUND` form: there the route exists and the requested object is not found. Outside `/v1/` the 404 body shape is unchanged.

**What integrators should do**

Branch on `error.code`, not on the body shape. A client that parsed the `message`, `error` and `statusCode` fields of the previous body on `/v1/` paths must switch to the unified `success` and `error.code` envelope.

### BC-0728-6: calendar: an unknown select name returns an error instead of an empty object

> Old format supported until: 28.07.2026

**Before**

[GET /v1/calendar-events](/docs/entities/calendar-events/list) (as well as `GET /v1/calendar-events/{id}`, `POST /v1/calendar-events/search` and calendar-events sub-calls of `POST /v1/batch`) silently returned `id`-only objects when `select` carried an unknown field name (for example `dateFrom` — no such field exists, the real name is `from`). The client believed it narrowed the payload while actually losing data.

**After**

An unknown field name in `select` returns `400 UNKNOWN_SELECT_FIELD` with the list of accepted names (`Available: …`). Bitrix24-style names (`DATE_FROM`) and date aliases (`updatedAt`) are still accepted and project their canonical key — only names that resolve to no schema field trigger the error. Other entities keep the previous behaviour: an unknown name produces a `meta.warnings` entry, not an error.

**What integrators should do**

Take field names from [GET /v1/calendar-events/fields](/docs/entities/calendar-events/fields) and remove non-existent names from `select` (`dateFrom`/`dateTo` → `from`/`to`). Clients that pass no `select` or pass valid names are unaffected.

### NEW-0728-7: calendar: occurrenceIndex and version fields

Calendar events gained two read-only fields. `occurrenceIndex` is the zero-based index of an occurrence within an expanded recurring series: the rows of a series share one `id`, and the `id` + `occurrenceIndex` pair uniquely identifies a row of the set. `version` is a monotonic change counter of the event — it grows on every modification and does not depend on the regional settings of the account. Diff recipe: request [GET /v1/calendar-events](/docs/entities/calendar-events/list) with `select=id,version`, compare the pairs against your snapshot, and re-read the changed events by `id`.

### NEW-0728-8: chats: limit clamp echo in meta

Three chat endpoints — [GET /v1/chats/recent](/docs/chats/discovery/recent), [GET /v1/chats/:dialogId/messages](/docs/chats/messages/list) and `GET /v1/chats/:dialogId/users` — now, when the passed `limit` is clamped into the allowed range, extend the response with a `meta` field carrying the `requestedLimit` and `appliedLimit` pair: what was requested and what was applied. When `limit` is within the range, `meta` is not added — the envelope is unchanged. For message reading, the real ceiling of cloud Bitrix24 is documented — at most 50 records per call regardless of `limit`, with continuation read via the `lastId` cursor.

### NEW-0728-9: mutual updatedAt and createdAt date-field aliases

Date fields in the entity catalog carry two naming families: some entities declare `updatedAt` and `createdAt`, others `updatedTime` and `createdTime`. Pair members are now accepted interchangeably on input: in `filter` and `select` — on every entity that declares the partner key, in `sort` — on entities with a camelCase field schema. For example, `updatedTime` on an entity with an `updatedAt` field works as `updatedAt`, and vice versa. Canonical field names in responses do not change — the alias applies to input only.

### NEW-0728-10: POST alias for the Knowledge base search

The Knowledge base 2.0 document search now also accepts `POST /v1/note/documents/search` with a JSON body `{ "query": "...", "limit": 20 }` — for agents that expect search to be a POST request by analogy with the other entities. The canonical form remains [GET /v1/note/documents/search](/docs/note/documents/search) with query parameters. Both forms accept only `query` and `limit`, and when a parameter is passed both in the body and in the query, the body wins.

### NEW-0728-11: feed: limit up to 200 records per request

[GET /v1/posts](/docs/feed/posts/list) accepts a `limit` from 1 to 200. The Bitrix24 feed page is fixed at 50 records — for a `limit` above 50 the platform stitches up to four pages into one response. A value above 200 answers with the previous `400 INVALID_LIMIT`. The `meta` object gained a `returned` field — the actual number of records in the response — and on a multi-page read `meta.nextOffset` is derived from the response window so page chaining continues as before.

### FIX-0728-12: calendar: honest offset and hasMore, deterministic order

**Before**

[GET /v1/calendar-events](/docs/entities/calendar-events/list) returned the head of the same set at any `offset`: Bitrix24 delivers the requested range as one unpaginated array, so every "page" repeated the first one, `meta.hasMore` stayed `true`, and the tail of the set beyond the first page was unreachable.

**After**

The full set is sorted deterministically — by the event start `from`, ties by `id`, then by `occurrenceIndex` — and an honest window from `offset` to `offset + limit` is returned from it. `meta.total` is the number of occurrences in the set: recurring events are expanded per occurrence, and the rows of a series share one `id`. `meta.hasMore` answers `true` only while records remain beyond the window. The element order in the response is now deterministic and may differ from the previous one.

**Impact on integrators**

Walking the set via `offset` now yields the whole range. Clients that deduplicated repeating pages on their own need no changes — there are no duplicates anymore.

### FIX-0728-13: select accepts declared Bitrix24 names and warns about unknown fields

**Before**

The `select` parameter understood only the canonical field names from `GET /v1/{entity}/fields`. A name in any other spelling — the original Bitrix24 name (`DATE_FROM`, `UF_DEPARTMENT`) or a different letter case — silently dropped out of the projection: the field was absent from the response with no error signal, and a request made of such names alone degenerated into objects with a single `id` field. Batch calls did not apply `select` at all: both the global [POST /v1/batch](/docs/batch) and the per-entity `POST /v1/{entity}/batch` returned full objects.

**After**

`select` accepts canonical names case-insensitively, declared original Bitrix24 names (`DATE_FROM` projects `from`, `UF_DEPARTMENT` projects `departmentId`) and the mutual date-field aliases — the response carries the field under its canonical key. An unknown name is no longer lost silently: list, search and get-by-id responses add a `meta.warnings` array with `{ "code": "UNKNOWN_SELECT_FIELD", "field": "<name>" }` entries — up to 10 warnings per response. Both batch calls now apply `select` to list and search operations the same way single endpoints do; get-by-id inside the global batch does not apply `select`. The global call additionally reports unknown-name warnings in the per-call `meta`. The per-entity call carries no warnings and performs no hard rejection of unknown names — an unknown name there is still simply absent from the response.

**Impact on integrators**

Single-endpoint responses are only extended. In batch calls, a client that passed `select` while reading fields outside of it will now receive only the requested fields — drop `select` from the call or list every field you need in it.

### FIX-0728-14: windowed search fails fast on an account-side timeout

**Before**

A `POST /v1/{entity}/search` with a wide date range is split into time windows. A Bitrix24 timeout on the first window was skipped, and the remaining windows each ran into their own timeout: the response took 60–75 seconds and then arrived as `503 BITRIX_TIMEOUT`. When later windows succeeded, a partial `200` with incomplete data was possible after the same minute of waiting.

**After**

A first-window timeout (`BITRIX_TIMEOUT`) now ends the request immediately: the `503` response with the `BITRIX_TIMEOUT` code and a `Retry-After` header arrives in about 15 seconds, and the remaining windows are not executed. A partial `200` after a first-window timeout is no longer possible — a deliberate trade-off: the windows are identical in shape, a timeout on the first predicts timeouts on the rest, and a partial response after a minute of waiting fed retry storms. A timeout on any later window is handled as before — the window is skipped and the response may be partial.

**Impact on integrators**

Retry the request per the `Retry-After` header. Clients that relied on a partial response under account overload now get a fast `503` — narrow the date range or retry later.

### FIX-0728-15: Galaxy .zip deploy: honest archive-extraction error instead of EMPTY_BUILD_CONTEXT

A Galaxy app deploy from a .zip now returns the real (sanitized) extraction-failure reason in the 502 `buildLog` instead of a misleading "EMPTY_BUILD_CONTEXT" / generic message.

### FIX-0728-16: the Bitrix24 operation-time-limit pushback now returns `OPERATION_TIME_LIMIT` with `Retry-After`

**Before**

When an account rejected a method that had exhausted its operating-time budget, the platform
answered `429 RATE_LIMITED` with `Retry-After: 2` and replayed the call up to three times.
Those retries could not help against an addressed refusal lasting minutes, and `Retry-After: 2`
was misleading: a client came back two seconds later and got the same refusal.

**After**

That pushback now returns the code `OPERATION_TIME_LIMIT` — the same code the account itself
emits — with `Retry-After` derived from the known lift time and a plain-language `userMessage`.
Retries are off: the restriction is addressed to one account-plus-key-plus-method triple, exactly
as Bitrix24 itself applies it, and until it expires the platform rejects calls of that method
itself, without contacting the account. Other methods of the account — and the same method under
a different key — are unaffected. Other `429` refusals (including `RATE_LIMITED` and
`QUEUE_OVERFLOW`) are unchanged.

Honor `Retry-After`: the same call cannot succeed sooner. Spread heavy reads over time or
narrow them — fewer fields, smaller pages, `POST /v1/batch`.

### FIX-0728-17: The * value in select returns every field

**Before**

The familiar Bitrix24 form `select: ["*"]` (and `["*", "UF_*"]`) produced the opposite result on single endpoints: `*` matches no declared field, so only `id` remained in the response. There was no error signal — the record simply came back empty.

**After**

`*` and `UF_*` (in any letter case) are recognised as a request for every field: no field selection is applied and the full record is returned. An unknown name passed next to the wildcard is not rejected — the response carries an `UNKNOWN_SELECT_FIELD` warning instead. This works the same way in list, search, get-by-id and both batch calls — [POST /v1/batch](/docs/batch) and `POST /v1/{entity}/batch`. On calendar events, where an unknown field name returns a `400` error, the `*` value is not treated as an error.

**Impact on integrators**

Nothing to change. A client that carried `select: ["*"]` over from Bitrix24 code will start receiving full records instead of objects with a single `id`.

### FIX-0728-18: windowed search stops once it has the requested rows and reports an incomplete window

**Before**

`POST /v1/{entity}/search` over a wide date range splits the range into time windows and merges their results. The walk went to the end of the range even when the requested `limit` rows had already been collected: a search with `limit: 50` over a range of several months read the whole range through — it answered slowly and put a load on the Bitrix24 account out of all proportion to the size of the answer. When a single window held more matching rows than one window read returns, the window returned only the beginning of its set, and did so silently: no signal in the response, and no way to read the remainder (windowed search rejects `offset > 0` with the `UNSTABLE_OFFSET_PAGINATION` code).

The second cause of incompleteness was silent too, on accounts where windows are read in packs: once 5000 rows are collected the search stops sending the remaining windows and returns a cut-off prefix — the response said nothing about that either.

**After**

The walk over windows stops as soon as it has collected more unique rows than `limit` asked for. The response still carries at most `limit` rows, and `hasMore` carries the "there are more" signal. On an early stop `meta.total` equals the collected count, so it is a lower bound on the number of matching rows rather than a full count over the range — exactly how this search already behaved on accounts that read windows in packs, and the behaviour is now uniform.

An incomplete answer is no longer silent — for neither of the two causes, and no matter whether windows are read one by one or in packs, **on entities whose list method Bitrix24 serves page by page**. The response gains a `{ "code": "WINDOW_TRUNCATED", "field": "…", "message": "…" }` warning in the `meta.warnings` array, where `field` is the range field the split was keyed on. The warning code is the same in both cases, so you can branch on it without parsing the text; `message` names the cause that fired: one window held more rows than a single window read returns, or the 5000-row ceiling was reached and the remaining windows were never sent.

The exceptions are named outright. Three entities will never get the warning, because Bitrix24 does not serve their list method page by page: files and folders (`/v1/files`, `/v1/folders`) and workgroups (`/v1/workgroups`). There the request goes out as a single call, `limit` is not forwarded to Bitrix24 at all, and the account returns a page of its own of about 50 rows: a window holding 200 disk objects comes back with 50 and stays silent, exactly as before this change. On pages (`/v1/pages`), sites (`/v1/sites`) and calendar events (`/v1/calendar-events`) the list method is not page-based either, but it returns the whole requested set in one call — there the warning is simply unreachable while nothing goes missing.

The load this search puts on a Bitrix24 account is reduced further: a degenerate lower bound on `id` is no longer forwarded to Bitrix24. Two forms are dropped, and they rest on different things. `>=` with a value of 0 or less, and `>` with a negative value, exclude negative `id`s only — they are tautological under a single assumption of non-negativity. `>` with exactly zero (`filter[>id]=0` — the one a cursor walk sends at its start) excludes the record with `id = 0`, so it additionally rests on Bitrix24 numbering records from one by auto-increment; that is the target case of this change, and it is dropped deliberately. The bound `>=id=1` is kept — the guard is deliberately narrow and looks only at values of 0 and below.

The set of records returned does not change on any entity where Bitrix24 really applies a filter on `id`. One known exception — pipelines (`/v1/categories`): one of them carries `id: 0` ("General"), but the pipeline list method ignores `filter` entirely, so both before and after this change the answer carries the full set of pipelines. The shape of the response is unchanged.

**Impact on integrators**

Do not read `meta.total` as an exact count of matching rows over a wide date range — on an early stop it is a lower bound; branch on `hasMore` instead. A windowed search cannot be read page by page (`offset > 0` is rejected), so "there are more" is answered either by a larger `limit` (up to 5000) or by a narrower date range.

Check `meta.warnings` for `WINDOW_TRUNCATED`: it marks an incomplete result, and paging does not cure that one either — narrow the date range or add filters so the search stops hitting a ceiling. `hasMore` alone is not enough for this: window splitting only engages at `offset === 0`, so a request for the next page goes out as a non-windowed one and yields a different result. A search over a narrow range, which is not split into windows, is unaffected.

Mind one boundary of windowed search: sorting applies WITHIN a window, not across the union of windows. Windows are built oldest to newest and concatenated in that same order, and the result is then cut down to `limit` — there is no global sort over the union. So a request with a descending sort and a small `limit` over a wide date range returns the OLDEST matching records, not the newest. The behaviour itself is not new, but the early stop rules out a post-merge sort as the way to fix it (records of later windows are no longer read at all), so it is stated here outright. For a true "last N by date" either narrow the range so that window splitting does not engage, or take the range in one request with a large `limit` and sort on your own side.
