For AI agents: markdown of this page — /docs-content-en/changelog/2026-07-29.md documentation index — /llms.txt
API changes: July 29, 2026
FIX-0729-1: task comments are no longer refused over key permissions
Before
POST /v1/tasks/:taskId/comments could return 403 on accounts with the new task card, quoting the account's own "insufficient scope" message — even with the task permission present on the key and neighbouring task calls answering 200 in the same second. The wording was a dead end: it read as "grant the app access to tasks", while the permission set is fixed when the key is issued, not by anything the user can change.
After
The key is now granted the comment permission in both spellings the old and the new Bitrix24 routers expect, so the call goes through. Keys issued earlier get their permission set pushed to the account on the first such refusal and the request is retried — no manual step. If the account still refuses, the 403 now states the real cause (the key's webhook carries a narrower permission set than the key itself) and points at re-issuing the key, instead of repeating the account's message.
Impact on integrators
No action required. A client that treated this 403 as a permanent error now receives 201.
FIX-0729-2: access changes for an app on a shared host now reach how it is shown in Bitrix24
Before
For an app on a shared host (kind=GALAXY_APP) embedded into the Bitrix24 interface, a change to the access list did not always reach the display. A user whose access had been revoked could keep seeing the app in its embedding slot — while access to the app itself was already closed.
This covered switching the access policy and editing the list via PATCH /v1/infra/servers/:id/access-policy, POST /v1/infra/servers/:id/access and DELETE /v1/infra/servers/:id/access/:accessId.
After
An access change now reaches the display: the app disappears from the Bitrix24 interface for those who lost access, and appears for those who were granted it.
Impact on integrators
No action required. Request bodies, responses and error codes are unchanged — only the observable effect of the call is different. Apps on a dedicated virtual machine are not affected: there the display already followed access.
FIX-0729-3: agents and bots no longer idle-sleep
Before
PATCH /v1/infra/servers/:id/sleep accepted any sleepAfterMinutes value, including servers created for an agent or bot (createdVia agent or bot).
After
For a server with createdVia agent or bot and a non-null sleepAfterMinutes, the endpoint responds 400 with code AGENT_IDLE_SLEEP_FORBIDDEN. The value null (never idle-sleep) is still accepted.
Impact on integrators
A sleeping agent or bot stops polling Bitrix24 and will not wake on a new message, so the previous value never worked — there is nothing to change in a working scenario. To save on a schedule, use Scheduled Wake.
BC-0729-4: the session in Authorization must belong to the app from X-Api-Key
Old format supported until: 29.07.2026
Before
A session (vibe_session_*) is issued to one app on one Bitrix24 account, but /v1/* never checked that binding. The authorization key of app B accepted a session issued to app A, and the request then ran with B's key rights — a foreign session opened data access through another app's key.
After
/v1/* verifies that the session in Authorization: Bearer belongs to the same app and the same Bitrix24 account as the key in X-Api-Key. A mismatch returns 403 with code SESSION_APP_MISMATCH. An app authorization key whose app is unlinked or deleted no longer accepts a session. Personal vibe_api_* keys are not affected by this change: they never read a session from Authorization — neither before it nor after.
Calls without Authorization (key only) are unaffected. A session presented with the key of its own app works as before.
Impact on integrators
Make sure both headers refer to one app: X-Api-Key must be the authorization key of the app that issued the session via POST /v1/oauth/token. If your service serves several apps, keep each key-and-session pair together and never source them separately.
The check is active from the release of this change, with no transition period: it closes data access through a foreign session. The 403 SESSION_APP_MISMATCH error is described in the error reference.
FIX-0729-5: an app on a personal key gets X-Vibe-Authorization again
Before
An app hosted in Black Hole on a personal key (vibe_api_*) lost the X-Vibe-Authorization header about a minute after the placement opened. The first requests carried a session, then it disappeared and never came back — not on a page reload, not on reopening the app — only a fresh placement open helped, and again only for a minute.
The cause: the session a placement issues to a user is bound to the app through its address (appUrl), but it was only ever recovered through the server → key → app chain. A personal key carries no app, so recovery answered "server not found" and the Gateway remembered that refusal. X-Vibe-User-Id kept arriving throughout, so from the app's side it looked like "the user is there but the token is gone".
After
When the key owning the server carries no app, the app is resolved by its address instead: among the apps of the same Bitrix24 account created by that key's owner, the one whose appUrl points at exactly this app address. The session is recovered and the header keeps arriving for the whole session lifetime.
Apps on an authorization key (vibe_app_*) behave as before — they do have the server → key → app chain, and it stays authoritative.
Impact on integrators
Nothing to change. If your app worked around this by reopening the placement or by caching the token itself, those workarounds can go.
NEW-0729-6: a management key whose owner account is pending erasure now returns 503
Before
The account freeze that applies while a data-erasure request is pending covered the Vibecode
dashboard and ordinary app keys, but not management keys: an owner whose account was pending
erasure kept issuing, rotating and deleting keys through /v1/keys.
After
A management key whose owner account is pending data erasure returns 503 with code
ACCOUNT_PENDING_ERASURE and a Retry-After: 3600 header — the behaviour ordinary app
keys have had for a while. Cancelling the erasure request makes the key work again with no
re-issue needed.
FIX-0729-7: galaxy app deploy verifies reachability and returns steps
Before
A successful galaxy app deploy via POST /v1/infra/servers/:id/deploy returned success: true, status: "running" with no data.steps[] and without verifying that the app actually answered over HTTP. A container that started but did not listen on its port still reported success — you could not tell a working deploy from a broken one. Also GET /v1/infra/servers/:id showed runtime: null and the default port for such an app — the deployed runtime and port were not persisted.
After
The success response carries data.steps[]: a { step: "build", status: "ok" } step plus, when the probe ran, a { step: "healthcheck", status: "ok" | "warning", httpCode, healthPath } step. status: "ok" means the app answered 2xx/3xx on data.appUrl; warning means it answered 4xx/5xx (still reachable, the deploy passed). A container that started but did not answer over HTTP on its port now fails honestly with 502 GALAXY_APP_START_FAILED (the same family as a crash after start — the response keeps a buildLog tail) instead of reporting success. The optional healthPath field (default /) in the deploy body sets the probe path. GET /v1/infra/servers/:id now reflects the deployed runtime and port.
Impact on integrators
An app that answers over HTTP on the deploy port is unaffected. An app that starts but does not begin answering within the probe window gets 502 GALAXY_APP_START_FAILED instead of a false success — make sure it listens on the port you deployed with and that healthPath returns a response. The build step in data.steps[] and the runtime/port persistence in GET are available immediately; the HTTP probe itself (the healthcheck step and 502 GALAXY_APP_START_FAILED) is rolling out — it is enabled gradually on the platform, so until it is active a deploy behaves as before (no probe).
FIX-0729-8: servers wake up after the debt is cleared on postpay accounts too
Before
A postpay account that went negative down to its overdraft limit had its servers stopped and tagged as billing-frozen. Topping the balance back up made the API return 200 again, yet the servers stayed off: POST /v1/infra/servers/{id}/wake kept refusing (SERVER_WAKE_BLOCKED), and only support could clear the tag. The same scenario already worked on prepay accounts.
After
As soon as the balance is no longer negative, the tag is cleared and the servers are woken automatically — identically on prepay and postpay. A partial top-up that leaves the balance negative re-opens the API but keeps the servers off: they come back once the debt is fully cleared. Servers stopped for other reasons (expired access, a manual stop) are left untouched.
FIX-0729-9: deploy no longer reports a false success after a hardened-unit rollback
Before
A deploy on POST /v1/infra/servers/:id/deploy could report success (healthcheck:ok, hardening:warning) while actually serving the response of a foreign process holding the app's port. This happened when the hardened unit failed, the deploy automatically rolled back to the plain unit, but the rollback did not free the port — and on the very first check the foreign port holder answered 200. The deploy reported success even though the new version never took the port and the previous version kept running in production.
After
If the same process that blocked the hardened unit still holds the port after the rollback (the reverted unit never took it), the deploy ends with healthcheck:error and an explicit message that the port is still held, instead of a false healthcheck:ok. A normal rollback, where a new instance of the app has taken the port, still succeeds.
FIX-0729-10: metadata cache now covers Bitrix24 account data and field schemas
Before
The cache for GET /v1/statuses was documented as personal-key scoped, and GET /v1/{entity}/fields schemas were not covered in the caching section. A client could not tell from the docs which repeated requests return X-Cache: HIT or how to request a fresh field schema.
After
GET /v1/statuses is documented as a 5-minute Bitrix24 account cache. GET /v1/{entity}/fields is documented as a 5-minute field-schema cache scoped by Bitrix24 account, authorization key, entity, path parameters, request parameters, and response language. Cache-Control: no-cache bypasses the cache for these reads, and /fields also supports refresh=true.
Integrator impact
No code changes are required. Repeated metadata reads put less load on the Bitrix24 account queue, and the X-Cache and X-Cache-Bypass-Reason headers show whether the cache was used.
NEW-0729-11: delta of recent dialogs through the updatedAfter parameter
GET /v1/chats/recent accepts updatedAfter — an ISO 8601 instant from which changed dialogs should be returned. This is a separate response mode: data arrives as a flat array of dialogs, and meta carries mode with the value delta and returned with their count.
The page size in this mode is owned by the server — one page of up to 200 dialogs is read. A passed limit does not affect it and comes back in meta.requestedLimit together with the applied meta.appliedLimit. When the delta could not be confirmed complete — Bitrix24 reported more dialogs beyond the returned page, or the response shape could not be parsed — meta carries truncated with the value true: in that case do not move updatedAfter, read the full list using the paged mode with the lastMessageDate cursor. The number of rows returned is not a completeness signal.
The boundary is inclusive — a dialog whose dateUpdate equals the given instant is included. The date has to carry an explicit offset or Z: a value such as 2026-06-29 10:00:00 reads differently depending on the server time zone and is rejected with 400 INVALID_PARAMS. The same code rejects updatedAfter combined with offset or lastMessageDate — paged navigation and the delta use different cursors.
NEW-0729-12: a transient 503 at the platform edge now says how long to wait
When every backend replica is momentarily unreachable — during a galaxy application redeploy, for instance — the platform edge answers 503 SERVICE_UNAVAILABLE on the /api/ and /v1/ prefixes. The body carried only the human-readable "Retry in a few seconds" and no machine-readable retry signal, so a client could not tell a seconds-long gap from a permanent outage and either failed the job or retried blindly.
That response now carries the Retry-After: 5 HTTP header and a retryAfter: 5 field inside the error object — next to code and message, exactly as the platform already does for its own transient 503. Existing calls are unchanged: the SERVICE_UNAVAILABLE code and the status stay put, and the header plus the field are added. The message text now also points at the header — you still should not parse it, branch on error.code. The full code list is at /docs/errors.
The same edge block also answers a read timeout from the backend. There the request DID reach the backend and may still be running, so for non-idempotent operations re-read the entity before retrying.
Worth stating what this does NOT do: it does not remove the reason the backend upstreams became unreachable. It makes the error honest and machine-readable so a client waits and retries correctly.
BC-0729-13: batch sub-calls reject a sort when the Bitrix24 method cannot do one
Old format supported until: 29.07.2026
Before
The single entity list and its POST /v1/departments/search already answered 400 INVALID_SORT_FIELD when the Bitrix24 method behind the list accepts no ordering. A sub-call of the global POST /v1/batch had no such check: the same sort went to the method, the method discarded it, and the sub-call returned success with an unsorted list. The same query therefore behaved differently on the two surfaces — refused through the single route, silent through the batch one.
After
A POST /v1/batch sub-call with action list or search now runs the same check and answers 400 INVALID_SORT_FIELD under that sub-call's errors entry, while the remaining sub-calls run as usual. The refusal is raised before the Bitrix24 call. Both spellings are checked — sort and order. Two entities are affected: departments and telephony-lines. Storages are NOT — their method can sort, and their refusal is lifted by a separate entry in this release.
What this means for integrators
If a sub-call to one of those two passed a sort, drop it — it never applied and the list came back in Bitrix24's own order. If you need a specific order, sort the returned list on your side. Sub-calls without a sort, as well as limit, offset, select and filtering, work exactly as before. There is no parallel support for the old behaviour: the old behaviour was the parameter being silently ignored, so there is nothing to keep.
BC-0729-14: `defaultOperatorData` on Open Channels is an object now, and empty means `null`
Old format supported until: 27.01.2027
Before
The field was declared an array, and an unset value was coerced to []. The real type is different: Bitrix24 returns an object shaped { "NAME": …, "AVATAR": … } when default operator data is set. So GET /v1/openline-configs and GET /v1/openline-configs/:id promised an array in fields while sending an object whenever the value was populated.
After
The field type is object; an unset value arrives as null rather than []. A populated value arrives as an object, as it already did. The neighbouring kpiFirstAnswerList and kpiFurtherAnswerList are genuine string arrays and are still coerced to [].
What integrators should do
Code that measured or iterated this field (defaultOperatorData.length, .map, .forEach) will break on null — switch the check to if (config.defaultOperatorData) { … } and read the object's fields directly. If you built against fields and expected an array, re-read the new object type.
BC-0729-15: telephony lines: writing `serverName`, sorting, filtering and offset no longer fail silently
Old format supported until: 29.07.2026
Before
serverName was declared a plain writable field, but Bitrix24 neither stores nor returns it: POST /v1/telephony-lines with that field answered 201 while the value vanished, and a PATCH of the same field hit an error from Bitrix24 itself. Ordering, filtering and offset behaved the same way: the Bitrix24 method behind this list accepts no input parameters at all, so ?order[number]=desc, ?name=…, ?filter[number]=…, ?offset=50 and the same values in the body of POST /v1/telephony-lines/search were dropped and the list came back 200 — looking sorted, filtered and paged while it was none of those. A sub-call of POST /v1/batch lost the sort the same way.
After
All four are now an explicit error raised before the Bitrix24 call. Writing serverName returns 400 READONLY_FIELD; any sort returns 400 INVALID_SORT_FIELD; any filter returns 400 UNSUPPORTED_FILTER; a non-zero offset returns 400 UNSUPPORTED_OFFSET. The filter refusal is raised on the list, in search, in POST /v1/telephony-lines/aggregate and in sub-calls of both batch endpoints — the global POST /v1/batch and POST /v1/telephony-lines/batch; the sort and offset refusals are raised on the list, in search and in a sub-call of the global batch (aggregate reads neither parameter). In the global POST /v1/batch the refusal arrives under that sub-call's errors entry while the remaining sub-calls run as usual; if every sub-call is refused the request answers 400 and the per-sub-call breakdown stays in errors. POST /v1/telephony-lines/batch differs: a filter-contract violation rejects the WHOLE batch with a single 400 naming the sub-call index, and no sub-call runs. The serverName field itself stays visible in GET /v1/telephony-lines/fields marked read-only, so its meaning is still discoverable.
What this means for integrators
If you sent serverName on create or update, drop the field — the value was never stored anyway. If you relied on sorting, filtering or offset, none of them ever applied; the list of an application's external lines arrives whole in a single page, so sort, filter and page it on your side. A plain list without those parameters, plus limit and select, works exactly as before. There is no parallel support for the old behaviour: the old behaviour was the parameter being silently ignored, so there is nothing to keep.
NEW-0729-16: the storages list can be sorted
Sorting by storage fields works on GET /v1/storages and in POST /v1/storages/search: ?sort=-id, ?order[name]=desc and the same values in the search body. A leading minus means descending.
The fields that change the order are id, name, entityType, entityId, rootFolderId — each was verified on a live account to return different results ascending and descending. The method also accepts code and module without an error, but on the accounts probed every storage carries the same value in those columns, so ordering by them changes nothing — do not rely on them as a sort.
Previously any sort of storages was rejected with 400 INVALID_SORT_FIELD. That refusal was a mistake: it had been inferred from the description of the Bitrix24 method, while the method does accept and apply an ordering — verified on a live account, where ascending, descending and unsorted results all differ. If you worked around the refusal by sorting the list on your side, that workaround can go; it keeps working either way.
The ordering also became stable. id is appended to your sort as a final key, and a list with no sort now arrives ascending by id — previously it arrived in the account's unspecified order. This is not cosmetic: the list is served 50 records at a time, and when sorting by a non-unique field (a name, say) a row sharing that value could land on two pages at once, or drop out of the result entirely, at a page boundary. The order is now total and unambiguous, which is also what makes paging over it repeatable. If you already sorted by id, your direction is preserved — no second key is added.
Storages are paged 50 records at a time, so when sorting, ask for the volume you need in one call (limit up to 5000) rather than walking pages by hand.
FIX-0729-17: restart a galaxy app via /reboot
Before
For a galaxy app (kind=GALAXY_APP), POST /v1/infra/servers/:id/reboot had no working path: the call returned 422 VM_MISSING (the container has no virtual machine of its own), and the only way to recover a stuck app was to delete it — which wipes the persistent /data volume.
After
/reboot restarts the app container as a self-recovery kick — the persistent /data volume is preserved. The call is accepted in the running or error status and returns an advisory verdict: restarted (the container was restarted) and healthy (the container came up and stopped restarting — a container check, not the app's HTTP response), plus a hint field when healthy: false about how to deploy a fixed version. The restart does not clear the error state — a crash-looping app is authoritatively reset by redeploying its source via POST /v1/infra/servers/:id/deploy. New error codes for this path: GALAXY_APP_REBOOT_USE_AGENT_CONTROLS (409 — an agent- or bot-backed app is managed from its own controls), GALAXY_APP_BUSY (409 — another command is running on the host, the response carries Retry-After), GALAXY_HOST_UNREACHABLE (502 — the host is unreachable). Rebooting a regular server is unchanged.
NEW-0729-18: totalDefault — the meta.total default on the API key itself
An API key gained a totalDefault setting: whether list calls made with this key request a count when the request itself passed no withTotal. A value of true means send meta.total, false means do not, and null means inherit the platform default. Every key starts at null.
The setting is edited in the dashboard on the keys page and through PATCH /v1/keys/:id with the totalDefault field (a vibe_live_ management key). Rotating a key through POST /v1/keys/:id/rotate preserves the setting, just as it preserves the access mode. A change is written to the audit log.
The value in force is visible in GET /v1/me — the totalDefault block shows the whole chain: key (the key setting), platform (the platform default), effective (what applies when a request passes no withTotal) and source, telling you where the effective value came from.
The setting is for cases where changing integration code costs more than flipping a key once: it sets the default for every list call made with that key at a stroke. The withTotal request parameter overrides it per call.
NEW-0729-19: meta.nextAfterId — the next-page cursor when sorting by id
Responses of GET /v1/{entity} and POST /v1/{entity}/search gained an optional meta.nextAfterId field — the identifier of the last returned record, as a string.
The field arrives when three conditions hold at once: the entity has a numeric identifier and supports cursor paging, the request sort is strictly id ascending, and meta.hasMore is true. On the last page the field is absent — there is nowhere left to go. Today the conditions are met by deals, leads, contacts, companies, quotes and smart-process items.
You pass the value back through the same filter cursor paging already used: filter[>id]=<nextAfterId> with the sort id ascending. No new request parameter appeared — the field only saves you from reading the identifier out of the last row by hand.
This kind of paging does not depend on an offset and does not get more expensive towards the end of a collection, so for walks of tens of thousands of records it is preferable to a growing offset.
NEW-0729-20: withTotal — a list call can decline the count
List calls gained an optional withTotal parameter. On GET /v1/{entity} it is a query parameter with exactly two accepted values — true and false; on POST /v1/{entity}/search it is a body field with a boolean value. Anything else reads as "the parameter was not passed", and no error is raised.
withTotal=false asks the platform not to count. Where that request can be honoured, no count is ordered from Bitrix24 and meta.total is absent from the response. Where the count cannot be avoided, the parameter has no effect and meta.total arrives as before. So check whether the field is present instead of assuming it.
Page by meta.hasMore — it is derived from page fullness and carries a "read while hasMore" loop to the end whether or not a count happened. When the sort is strictly id ascending, the response also carries meta.nextAfterId, which you pass back in filter[>id].
If the parameter is absent, the value comes from the API key setting, and failing that from the platform default. The value in force right now, and the whole chain behind it, is shown by the totalDefault block in GET /v1/me.
When you genuinely need an exact count, ask for it directly: POST /v1/{entity}/aggregate with the count function returns the number in one call without fetching any records. Do not emulate a counter by walking the collection page by page — that is dozens of calls instead of one, and the most expensive way to learn a single number.
FIX-0729-21: meta.hasMore in lists is derived from page fullness, and meta.total may lag by up to a minute
Before
meta.hasMore in GET /v1/{entity} and POST /v1/{entity}/search responses was derived from meta.total: "there is more" meant "offset plus the returned rows is below the overall count". While the count was recomputed on every call, that matched reality.
After
The platform stops asking Bitrix24 to recount on every repeated call with the same key and query — counting is disproportionately expensive for the account. That has two observable consequences.
meta.hasMore on such responses is derived from page fullness: a full page means "there may be more", a short page means the list has ended. A "read while hasMore" loop still always reaches the end. When the collection size is an exact multiple of limit, the last step returns an empty list — that is the normal end-of-list signal.
meta.total stays a number and stays in place, but becomes informational: it may lag by up to a minute, so the number of returned rows can exceed it.
Impact on integrators
Nothing to change if you page by meta.hasMore — that is the recommended way. If your code treats meta.total as an exact loop bound, or asserts that the returned rows never exceed it, switch to meta.hasMore. For an exact count at request time use POST /v1/{entity}/aggregate with the count function.