For AI agents: markdown of this page — /docs-content-en/changelog/2026-07-27.md documentation index — /llms.txt
API changes: July 27, 2026
NEW-0727-1: app favicon in one line — /_gw/icon
The browser-tab favicon is now set with a single static line that carries no server id:
<link rel="icon" href="/_gw/icon">
/_gw/icon is a platform-served path on the app's own origin; it always returns the currently uploaded icon. One upload via POST /v1/infra/servers/:id/icon drives both the Bitrix24 catalog card and the favicon: re-upload the icon and the favicon refreshes on its own (~5 minutes), no rebuild needed. You no longer need to ship your own static icon file in the app. The line is compatible with one-shot app creation (no id required).
Additionally, the POST /v1/infra/servers/:id/deploy response and the one-shot create POST /v1/infra/servers response now return a warnings[] entry when no icon has been uploaded yet — with the exact upload endpoint (the icon is a separate request, since the id only exists after create). The same warnings[] still flags a missing displayName/description. A successful deploy that left the app icon-less or unnamed is no longer silently treated as finished.
NEW-0727-2: source-at-create: SOURCE_AT_CREATE_GALAXY_ONLY now carries a hint with a path to a galaxy
A POST /v1/infra/servers refusal with source that could not be placed in a galaxy (a both-mode account with no open galaxy host, or a standalone-only account) now additionally carries error.hint — an actionable path: how to get a galaxy host and/or how to deploy to a dedicated server in two steps. The error code and message are unchanged.
FIX-0727-3: galaxy apps: PATCH /sleep and PATCH /port now return 400 — manage them from the Galaxies page
Before
For an app hosted in a galaxy (GALAXY_APP), PATCH /v1/infra/servers/:id/sleep returned 200 and wrote sleepAfterMinutes, and PATCH /v1/infra/servers/:id/port returned 404/409 — contradicting the documented /v1/me (deployment.galaxyApp) contract, where no V1 lifecycle action applies to a galaxy app.
After
Both calls for a galaxy app return 400 with error.code = "GALAXY_APP_USE_GALAXY_ROUTE" and change nothing. Configure the app's auto-sleep via the galaxy route; a galaxy app's port is host-pinned and not settable. Standalone-server behavior for /sleep and /port is unchanged.
FIX-0727-4: galaxy app recovers after its host tunnel drops
Before
When a galaxy host's secure tunnel dropped (the server still RUNNING, but the connection lost), deploying and running commands for a galaxy app returned 502 GALAXY_HOST_UNREACHABLE, and a logs request returned an empty response with a hint. The host stayed unreachable until a manual repair: retrying the same request hit the same error indefinitely.
After
The platform now restores the host's tunnel in the background without holding up the response. Retrying the same request succeeds as soon as the host reconnects (usually within a minute). Parallel deploys/commands against one shared host do not start a duplicate recovery.
Impact
The error code and response shape are unchanged — 502 GALAXY_HOST_UNREACHABLE (for deploy and exec) is still marked retryable, and logs still return an empty list with a hint. What changed is that a retry now succeeds instead of failing forever. Keep retrying on that code and on the empty logs response with your usual policy.
Affected endpoints: POST /v1/infra/servers/:id/deploy, POST /v1/infra/servers/:id/exec, GET /v1/infra/servers/:id/logs
NEW-0727-5: warning when a deploy changelog has nowhere to be published
The POST /v1/infra/servers/:id/deploy response now carries an entry in warnings[] when the request included a changelog but the deploy produced no new source version. A release note is bound to a version, so without one the text is stored nowhere and never reaches the app's channel feed in the Bitrix24 messenger.
The reason is visible in the source field of the same response: source storage is disabled (feature-disabled-platform or feature-disabled-portal), the save failed (save-failed), or the uploaded bytes matched the previous version. Such a deploy used to answer with a plain success, leaving no way to learn the note was lost. The warning is delivered both in JSON mode and in the done event when ?stream=true is used.
FIX-0727-6: bizproc-templates list without an explicit select now returns every field, including id
Before
GET /v1/bizproc-templates and POST /v1/bizproc-templates/search without an explicit select returned only the documentType field. Without id a client could not issue a follow-up update/delete — the list was useless without a second request carrying an explicit select.
After
Both calls without a select return the full declared field set (id, moduleId, entity, documentType, autoExecute, name, description, modified, isModified, userId). An explicit select works as before. The request format is unchanged.
NEW-0727-7: uploading a file to a knowledge-base document now returns assetMarkdown right away
An upload via POST /v1/note/documents/{documentId}/files used to return only { id }, so getting the ready-to-embed block meant a second call to GET /v1/note/documents/{documentId}/files/{id} — or assembling the [[image fileId=N]] markup by hand.
The response now carries the whole file object — id, documentId, name, size, mimeType, assetType, assetMarkdown — the same shape GET returns. Upload the image, take assetMarkdown from the response, put it into the document body and call PATCH: no second request needed.
The change is additive: id stays in place with the same value, so a client reading only that field keeps working unchanged. If a particular Bitrix24 account returns an object without assetMarkdown, the platform invents nothing — the response carries exactly what Bitrix24 sent.
FIX-0727-8: `ttlSeconds` bounds for access tokens in the machine-readable schema now match the behaviour
Before
The OpenAPI schema for POST /v1/infra/servers/{id}/access-tokens advertised ttlSeconds between 60 seconds and 30 days. The platform, from day one, accepted 300 seconds up to 315,360,000 (ten years) and rejected anything outside that with 400 INVALID_TTL. So a client — or a client library generated from the schema — that took the minimum straight from the schema hit a hard error on a value the schema itself offered, while the "unlimited" option from the interface looked unavailable through the API. The prose documentation was correct all along; only the machine-readable schema disagreed.
After
The schema reads its bounds and default from the very constants the request is validated against, so the two can no longer drift: minimum 300, maximum 315,360,000, default 86,400.
What this means for integrators
The endpoint's behaviour did not change — only what the machine-readable schema says about it. If you generate a client from the OpenAPI document and it validates ttlSeconds on your side, regenerate it: the old client would reject valid values above 30 days and allow values below 300 seconds that the platform always refused.
NEW-0727-9: A placement app can auto-resize its iframe
An app embedded in a placement can now report its content height to the platform, which grows the iframe to fit it — previously the height was fixed by Bitrix24 and tall content was clipped. The app posts a message to its parent window: window.parent.postMessage({ type: 'vibe:resize', height: <pixels> }, '*'). The accepted type is vibe:resize or vibe:setHeight with a numeric height field; targetOrigin must be '*' — the browser checks it against the window's immediate parent. Recompute the height when the content changes, for example with a ResizeObserver. A full description and recommendations are in the "iframe auto-height" section of the app runtime guide.
The feature is activated per Bitrix24 account on the platform side; if the resize does not take effect yet, it is not active for your account.
FIX-0727-10: list offset now counts records, not pages
Before
Bitrix24 returns lists in pages of 50 and reads the offset as a page ordinal, not a record count. We forwarded offset verbatim, so it was silently floored to a multiple of 50: offset=0, offset=7 and offset=49 all returned the same first page — no error, no warning. Walking a selection in steps smaller than 50 records looped on the first page, while a step of exactly 50 worked and made the parameter look healthy.
After
offset counts records on the generic list endpoints: GET /v1/{entity}, POST /v1/{entity}/search, POST /v1/batch (action list) and GET /v1/{entity}/{id}/activities. offset=7 starts at the 8th record. Offset and limit are independent: ?limit=2&offset=51 returns exactly two records starting at the 52nd. Bitrix24 still pages by 50 — Vibecode fetches the page covering the requested position and trims the head; the cost is at most one extra Bitrix24 page per request.
Two consequences are fixed alongside. meta.hasMore accounts for the offset: previously, for entities whose list Bitrix24 returns under a named key — deals, contacts, companies, leads, tasks, orders, products, invoices and others, two dozen in all — it compared only the page length against the total and stayed true on the last page whenever offset was non-zero. GET /v1/{entity}/{id}/activities now honours limit: the underlying Bitrix24 method accepts no size limit, so the whole page used to come back regardless of the requested value.
When the requested position lands past what Bitrix24 returned for the fetched page — because it filters after paginating — the response carries meta.warnings with code OFFSET_BEYOND_FETCHED_PAGE; this used to surface as an unexplained empty list.
For deep paging over large selections a keyset cursor (filter[>id] with order[id]=asc) is still more reliable than an offset: it is depth-independent and stable under concurrent writes.
Impact on integrators
Nothing to change: an offset that is a multiple of 50 behaves exactly as before, and code that already used it that way keeps working untouched.
Two things are worth checking. First, GET /v1/{entity}/{id}/activities with an explicit limit: it used to return the whole page (up to 50 records) regardless of the value, and now returns exactly what was asked for. If your code relied on getting more than it requested in one call, raise the limit or page through the selection. Second, walking a selection in steps smaller than 50: that used to loop on the first page and now moves forward, so a loop that leaned on an extra counter-based exit will start returning new records.
Residual exceptions where the offset is NOT row-exact. First, four generic-layer entities whose Bitrix24 method rounds the offset down to a page boundary and whose selection cannot be widened beyond the requested size without risking lost records: calendar-events, calendar-sections, telephony-lines, workgroups. The offset there is unchanged. Second, individual endpoints with their own handlers, which were out of scope for this change: /v1/warehouses, /v1/bookings, /v1/posts, /v1/requisite-links, /v1/lists, /v1/timeline-logs and the stage history under /v1/crm-extras. Their behaviour is unchanged.
The four generic-layer entities above keep their previous offset behaviour, but their meta.hasMore is now more accurate: on the last page with a non-zero offset it could previously say "no more" while records remained.
FIX-0727-11: a closed feedback ticket no longer reports itself as "in progress"
Before
A ticket caught by probe-campaign detection reported status: NEW to its author regardless of what had actually happened to it. The list filter runs on the real status, so a closed ticket both passed the "Solved" tab filter and rendered an "in progress" badge — the same ticket contradicting itself. Affected GET /v1/feedback and GET /v1/feedback/{id}.
After
Only the state the mask exists for is masked: the auto-archive. A closed ticket returns RESOLVED, a ticket in progress returns its real status, and an auto-archived one still arrives as NEW. Keys with feedback access (management, vibe:feedback scope) see the real status as before.
What this means for integrators
A client that read status and expected NEW for such a ticket now gets its actual status — that is the fix. In addition, on a detection-flagged ticket that is already closed, a withdrawal (PATCH {"status":"WITHDRAWN"}) and an author reply are now rejected with 409 FEEDBACK_CLOSED, like on any closed ticket; they used to be accepted because the gate consulted the masked status.
FIX-0727-12: Connect keys: stored scopes are authoritative — vibe:ai / vibe:search are no longer added automatically
Before
A key issued via Vibecode Connect automatically gained the platform scopes vibe:ai and vibe:search on every request, even when they were neither requested nor consented. Such a key could reach the AI endpoints (/v1/chat/completions, /v1/ai/*) and the Search endpoint (/v1/search), and the spend was charged to the billing account the Bitrix24 account is bound to.
After
The scopes stored on a key are now authoritative — the platform no longer widens them automatically. A key issued via Connect reaches the AI and Search endpoints only when the corresponding scope is actually present on the key; otherwise the response is 403. Rotating a key preserves this property. Keys created in the dashboard are unchanged.
FIX-0727-13: Apps: a derived key and app-scope sync grant no more than the calling key holds
Before
Calling POST /v1/apps with an authoritative key (issued via Vibecode Connect, or derived from one) minted the app's paired key with the full set of default platform scopes (vibe:infra, vibe:ai, vibe:search, vibe:storage) — even when the calling key held none of them. Likewise PATCH /v1/apps/:id could write a vibe:* scope into the app declaration that the key does not hold. In both cases the derived key gained the ability to reach AI and Search, charged to the billing account the Bitrix24 account is bound to.
After
A derived key receives exactly the platform scopes the calling key actually holds. When an authoritative key lacks a scope, a request carrying it in the body returns 403 SCOPE_GRANT_REQUIRES_CONSENT, with the unconsented scopes listed in error.details.unconsented. Scopes the key does hold (for example a consented vibe:storage) pass through as before. The derived key inherits the caller's expiry. App-scope synchronization to paired keys no longer adds vibe:* to an authoritative key — scope narrowing still applies. Keys created in the dashboard are unchanged: they still receive the default platform scopes.
FIX-0727-14: Bitrix24 call timeout is now configurable, the internal retry after a timeout is removed
Before
The platform always aborted an HTTP call to Bitrix24 at the 15-second mark, and for read methods issued one internal retry after the abort — up to ~30 seconds before the 503 BITRIX_TIMEOUT response. The retry also started a second concurrent execution of the same call on Bitrix24: dropping the connection does not stop Bitrix24 from processing the request.
After
- The time limit of a single Bitrix24 call is now platform-configurable (default stays 15 seconds). On accounts where Bitrix24 responds slowly the platform can raise the limit — requests that previously ended in a stable
503 BITRIX_TIMEOUTnow live to the real answer and return data. - The internal retry after a timeout is removed for all methods.
503 BITRIX_TIMEOUTon reads arrives roughly twice as fast (~15 seconds instead of ~30), and the request is no longer executed on Bitrix24 twice.429(rate limit) retries are untouched. - The error text
Bitrix24 did not respond within 15snow carries the actual limit (e.g.within 60s) — do not rely on the constant in the text.