For AI agents: markdown of this page — /docs-content-en/changelog/2026-07-28.md documentation index — /llms.txt
API changes: July 28, 2026
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 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 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 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 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 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 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 response the api._rules block gained three new pointers — to task checklists, Open Channels and 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:
{
"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:
{
"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 (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 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 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, GET /v1/chats/:dialogId/messages 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 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 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 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 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 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 ids 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.