For AI agents: markdown of this page — /docs-content-en/changelog/2026-08-28.md documentation index — /llms.txt
API changes: August 28, 2026
FIX-0828-1: stopping a server is no longer billed as a full running hour
Before
After a server was stopped via POST /v1/infra/servers/{id}/stop, the transition to
sleep was not recorded in the status journal. When the hourly charge for that hour ran
late, it relied on the last known record — running — and billed a full hour at the
running rate, including the time the machine was already stopped. The endpoint response
itself was correct; the discrepancy showed up only on the invoice.
After
The transition is recorded immediately, and the hour is billed as it actually happened: running time up to the stop, sleeping time after it. The response shape and status codes are unchanged, and the HTTP 200 response is preserved.
FIX-0828-2: a late upload through a valid URL preserves the object
Before
If an upload through a signed URL started more than one hour after issuance, its reservation could be deleted before the URL expired. A later completion returned 404, while the uploaded bytes remained without an API object.
After
An empty reservation remains until the maximum signed URL lifetime expires. The successful completion response remains unchanged; a late upload through a still-valid URL remains linked to its API object.
FIX-0828-3: GET /v1/guide returns the paging rules as a single paginationCanon block
Before
The paging and record-counting rules were repeated inside every entity of the response. The operations.search.paginationStability fields and the operations.search.params.withTotal description carried the same text for every entity.
After
The same text arrives once, as a top-level paginationCanon block. The wording is carried over verbatim — no rule was rewritten or shortened. The same-named fields inside the entities are still present and carry a pointer to the matching paginationCanon entry.
The one exception is operations.search.paginationStability.counting. It still arrives per entity with its own text, because how to obtain an exact count depends on whether that entity offers the aggregate operation.
Impact on integrators
Calls work exactly as before and no response field was removed. If your code read the rule text out of the entity fields, read it from the paginationCanon block instead. On a full set of scopes the response is roughly 37 percent smaller.
FIX-0828-4: task comment filter and sort accept camelCase field names
Before
GET /v1/tasks/:taskId/comments accepted only raw Bitrix24 field names in filter and sort. The names authorId and createdAt, in which the same fields arrive in the response, returned 400 UNKNOWN_FILTER_FIELD or 400 INVALID_SORT_FIELD.
After
The filter and sort parameters accept camelCase names alongside raw ones: id, authorId, authorName, createdAt, and sort additionally accepts authorEmail. Card-type limits are unchanged: filtering by authorName and sorting by authorName or authorEmail are still accepted only on the old card and return 400 on the new one. When filter names one field twice in different spellings of the name or of the equality sign, the response is 400 INVALID_FILTER. Unknown names still return 400.
Impact on integrators
Filtering and sorting can use authorId and createdAt — the same names in which these fields arrive in the response. Existing requests with raw field names require no changes.
BC-0828-5: Deploy outcomes can be confirmed after a dropped connection
Old format supported until: not provided
Before
If the gateway connection dropped during a deploy, the client received neither the outcome nor the operation identifier. A repeated deploy could encounter EXEC_BUSY without answering the main question: whether the first deploy completed.
After
POST /v1/infra/servers/:id/deploy stores an observable standalone or galaxy deploy outcome, while GET /v1/infra/servers/:id/operations returns recent operations started by the current API key even when the terminal response carrying operationId was lost. For GATEWAY_UNREACHABLE, GATEWAY_CONNECTION_TERMINATED, GATEWAY_STREAM_ERROR*, GATEWAY_TIMEOUT*, TUNNEL_NOT_FOUND, and a post-drop Galaxy interruption, the outcome is stored as unknown, and error.hint requires reconciling the operation, server, and logs first. On standalone this applies equally when the transport throws and when the gateway reports a timeout/error frame in-band during exec or upload/download; that unconfirmed outcome carries error.retryable: false and does not trigger an internal retry. After confirmed EXEC_BUSY, a standalone server may use /unstick only when no operation is still running; tenant recovery is unsupported for a shared Galaxy host, so wait and contact support if the refusal persists.
What integrators should do
When error.retryable: false, do not resend the same deploy: read GET /v1/infra/servers/:id/operations, the server state, and logs first. Retry only after a durable failed outcome when the response hint explicitly permits the same request. Do not delete or recreate the slot while the outcome is unknown.
FIX-0828-6: task comment creation returns the correct numeric ID
Before
On the new task card, POST /v1/tasks/:taskId/comments and POST /v1/tasks/:taskId/comments/batch with action: create could return id: null for a found message when its numeric identifiers arrived as strings. With multiple matches, string comparison could select the wrong ID.
After
Both endpoints return the found message's numeric id and compare matching IDs as numbers. The single POST response remains HTTP 201. The batch response remains HTTP 200, and a successfully created item remains success: true.
If the search does not find the message or cannot identify it unambiguously, id: null remains a valid successful result: the comment has already been created, and retrying the request may create a duplicate.
Impact on integrators
No client changes are required. Continue supporting number | null and do not treat null as a failed creation.
BC-0828-7: incomplete public uploads are no longer anonymously accessible
Old format supported until: not provided
Before
For a PUBLIC object in the PENDING state, GET /v1/public-storage/{portalId}/{objectId} returned 302, while HEAD /v1/public-storage/{portalId}/{objectId} returned 200. The file became accessible without authentication before the upload was completed.
After
Both requests return 404 while the upload remains in the PENDING state. Anonymous access is available only for a PUBLIC object whose upload is complete.
What integrators should do
After uploading the file, call POST /v1/storage/objects/complete and publish the anonymous link only after a successful response.
FIX-0828-8: MCP returns the Drive file content instead of a network error
Before
The download action of manage_file parsed the GET /v1/files/:fileId/download response as service JSON, although that request returns file bytes. On any real file the agent got success: false with code NETWORK_ERROR and an Unexpected token … message — the first bytes of the already downloaded file presented as a connection failure. The action had no working answer at all.
After
The action returns content with the file content in base64, plus contentType, size and filename — mirroring how upload accepts content. Files above 10 MiB are refused with code FILE_TOO_LARGE, and the message names the way to get such a file: a request to the same address with the same API key. Error responses stay the usual platform envelope: a missing file is ENTITY_NOT_FOUND, not NETWORK_ERROR. The generic call_api, which only reads JSON envelopes, now refuses known non-JSON routes up front with NON_JSON_RESPONSE_UNSUPPORTED instead of making a request it cannot parse. It refuses the browser entry points of the OAuth and connect flows the same way, with REDIRECT_ROUTE_UNSUPPORTED: they answer with a redirect, and the request used to travel there with the API key and then follow that redirect to an address the platform does not choose. The path is reduced to its canonical form before the check, so spelling it with .. no longer slips past the refusal, and a full address in place of a path is refused with INVALID_PATH. The download action now requires the file id to be a positive whole number: a fractional value used to return another file's content as a successful answer. The REST request itself still returns binary data, so ordinary clients work as before.
FIX-0828-9: concurrent direct uploads of one object are coordinated
Before
Concurrent POST /v1/storage/objects/upload calls for the same physical app address could mix file content and its metadata.
After
Path A direct uploads with an app-bound key run one at a time. After a turn is acquired, the response remains HTTP 200. If safe write ownership is not confirmed within 60 seconds or the concurrency limit, the request receives 409 STORAGE_KEY_CONFLICT before writing to object storage. Retry the entire request.
The guarantee applies only to Path A with an app-bound key. It does not extend the behaviour of personal keys (appId = null) or Paths B/C.
Impact on integrators
The successful response format is unchanged. On STORAGE_KEY_CONFLICT, retry the original request in full.
FIX-0828-10: create and update response specifications reflect the identifier
Before
The OpenAPI specification and response examples for create and update operations promised a full entity where the API returns only data.id.
After
OpenAPI and the documentation describe the actual response with data.id. The HTTP 201 status for create, the HTTP 200 status for update, and API runtime behavior are unchanged.
Affected endpoints: POST /v1/bizproc-activities, PATCH /v1/bizproc-activities/:code, POST /v1/bizproc-robots, PATCH /v1/bizproc-robots/:code, POST /v1/bizproc-templates, PATCH /v1/bizproc-templates/:id, POST /v1/calendar-sections, PATCH /v1/calendar-sections/:id, POST /v1/telephony-lines, PATCH /v1/telephony-lines/:number.
Impact on integrations
Existing requests require no changes. Regenerated clients now see the actual successful response shape.
FIX-0828-11: commands in a galaxy app are no longer refused before dispatch
Before
POST /v1/infra/servers/{id}/exec on a galaxy app answered 502 with code EXEC_FAILED and a message about safe container execution not being supported. The refusal hit every app and depended neither on the command nor on the state of the container: the request never reached the container at all.
After
The command runs in the app container, and the successful response remains HTTP 200 with the exitCode, stdout, stderr and duration fields — the same shape as on other servers. The CONTAINER_NOT_READY code for a stopped or missing container is returned where the platform can confirm that state; where it cannot, the reason arrives in the stderr of the result and the response itself is still HTTP 200.
FIX-0828-12: Node.js 20 runtime templates install more reliably
Node.js 20 in node20* templates is now installed more reliably when external network access is unavailable.
Before
The template performed an unbounded NodeSource installation. On a network failure, Node.js 18 could be installed, and the deploy then failed only with a generic version-check error.
After
The NodeSource installation uses bounded network attempts and an integrity check. If it fails, its package source is temporarily isolated, pre-existing configuration is preserved, and a verified Node.js 20 archive is used as a fallback; runtime identifiers and the deploy API stay unchanged.
FIX-0828-13: A ZIP deploy no longer asks you to change the archive format when the server is busy with another command
Before
While another command was running on the server, POST /v1/infra/servers/:id/upload with a zip and extract: true, and a galaxy-app deploy from a zip, reported a failed unzip install (UNZIP_PREFLIGHT_FAILED / a build failure) and advised re-packing the archive as .tar.gz.
After
A short-lived busy command channel is retried by the server. If the channel is still busy, the response is 409 with code EXEC_BUSY (on a galaxy-app deploy — GALAXY_APP_BUSY), retryable: true, and a Retry-After header. Do not change the archive format. A real failure to install unzip is still 502 UNZIP_PREFLIGHT_FAILED and the .tar.gz advice.
Impact on integrators
Retry using Retry-After. Do not re-pack the archive because of EXEC_BUSY / GALAXY_APP_BUSY. The UNZIP_PREFLIGHT_FAILED branch is unchanged.
FIX-0828-14: a payment with an inflated vibe count is held instead of rejected
Before
A payment.paid event whose metadata.tokens was ABOVE the catalog count received
HTTP 400 with code TOKENS_MISMATCH. The rejection was terminal: no retry follows
for that event, while the payer has already been charged — the credit never happened
and the payment surfaced in no review queue.
After
Such an event is accepted as held: HTTP 200 with body
{ "ok": true, "held": true, "reason": "TOKENS_ABOVE_CATALOG", "vendor_reason": "catalog-drift-over" }.
No Vibe credits are added — the payment enters the review queue, the same way the
opposite-direction TOKENS_BELOW_CATALOG hold does. The TOKENS_ABOVE_CATALOG value
of reason is new; the two directions carry separate dictionary names because an
overpayment and an underpayment owe different amounts.
The downward direction (TOKENS_BELOW_CATALOG) is unchanged. An unreadable count is
still rejected with BAD_TOKENS.
FIX-0828-15: a pending-deletion owner's key no longer calls Bitrix24 methods through V1
Before
A Bitrix24 account owner's key with scheduled self-deletion could still call some Bitrix24 methods through V1. The restriction did not cover every generated and handwritten wrapper, REST 3.0 method, direct call, and batch request.
After
All V1 methods that use an account-bound APP/OAuth key to send requests to Bitrix24 return 503 user_self_deletion_pending with Retry-After before dispatch. This includes GET /v1/lists, GET /v1/calendar/settings, GET /v1/chats/recent, GET /v1/workday/status, GET /v1/applications, POST /v1/apps, tariff-refresh and trial-activation methods, and POST /v1/batch.
For a pending-deletion owner's READONLY key, this 503 takes precedence over WRITE_BLOCKED_READONLY_KEY. A fully authenticated active owner's key still receives the READONLY rejection, while earlier authentication and account-state errors keep their existing codes.
V1 metadata methods, including plain GET /v1/me without refresh=tariff, the MANAGEMENT control plane, and methods that operate only on Vibecode platform data are not frozen by this change.
Impact on integrators
No integration changes are required. On a 503 response, retry no earlier than the Retry-After value.
BC-0828-16: include now requires the scope of the related entity
Old format supported until: not provided
Before
The include parameter did not check whether the key held the scope of the entity it pulled in. Only the entity you were reading was checked. A key holding just tasks could therefore read a full employee record through GET /v1/tasks/{id}?include=responsible, while the same key was refused on /v1/users.
After
Both sides of the relation are checked. When the scope of the related entity is missing, the request is refused with code SCOPE_DENIED and HTTP 403, and the message names the missing scope and the relation that needed it.
This affects relations whose two sides require different scopes: responsible and creator on tasks and owner on workgroups require user, while catalog on product sections requires catalog. Relations that stay within one scope — preset on requisites or parentSection on product sections, for example — are unchanged.
If your integration uses such a relation, add the missing scope to the key: the scope set is editable on the key itself, there is no need to reissue it.
Affected endpoints: every endpoint that accepts include — read by identifier, list reads and POST /search. The mechanism itself — including related records.
NEW-0828-17: include for requisites, workgroups and product sections
Three reference entities got their relations, so the include parameter is now declared and works on them. Requisites gained preset, the requisite preset that defines the field set. Workgroups gained owner, the record of the employee who owns the group. Product sections gained two at once: catalog, the trade catalog the section belongs to, and parentSection, the parent section, which comes back empty for a top-level section.
The related record still arrives in the _included field next to the record itself, on list reads, on read by identifier and in the POST /search body. The mechanism limits are unchanged: at most three relations per request, and on a selection of more than 200 records relations are not resolved and the answer is marked includeSkipped.
The other reference entities still do not declare include, and that is a decision rather than an omission. Bitrix24 returns deal pipeline stages as a string code instead of a reference-record identifier, so there is nothing to join them by. On a reference record the pipeline number is meaningful only together with the CRM object type, and one and the same number belongs to a deal pipeline and to a smart-process pipeline at the same time. On a document template the file identifier is not addressable through Drive methods, and the numerator is not exposed as a separate entity in the API. The workgroup member list is served by its own endpoint rather than through include.
Affected endpoints: GET /v1/requisites, GET /v1/workgroups, GET /v1/product-sections, their read by identifier and POST /search. The mechanism itself — including related records.
FIX-0828-18: select combined with include no longer empties the relation on list and search
Before
When a list request or POST /search carried both select and include and the foreign key was not named in select, the related record came back empty. GET /v1/deals?select=id,title&include=company returned an empty _included.company, even though the same relation resolved without select. Read by identifier was unaffected.
After
The relation is resolved before the answer is narrowed to the requested fields, and the foreign key is added to the internal request on its own. The public answer is not widened by it: it still carries only the fields you named, plus the _included block.
Affected endpoints: list reads and POST /search on any entity that declares relations. The mechanism itself — including related records.
NEW-0828-19: the current-user profile is now in the specification and the reference
The method GET /v1/users/me already worked, but it was not declared in GET /v1/openapi.json, so it reached neither the API reference nor the endpoint map. A client checking against the specification did not find the method and concluded it did not exist.
The operation is now described: scope user, the same response shape as GET /v1/users/:id plus a tri-state isAdmin field. The behaviour of the method itself is unchanged.
isAdmin is null when the check could not be completed. The profile is still returned in full, so gate on isAdmin === true rather than on a negation.
FIX-0828-20: `topUpAvailable` for a self-hosted account reflects the self-hosted checkout
Before
GET /v1/cowork/subscription/preview answered topUpAvailable: true for a self-hosted account whenever sales were open in its region at all. Self-hosted accounts buy through a separate checkout that opens separately, so the flag could promise a top-up that the very next order request refused.
After
The flag is computed from the availability of the self-hosted checkout in the account's region. The HTTP 200 response is unchanged and carries the same fields; for cloud accounts the value did not change. While the self-hosted checkout is closed, the answer is topUpAvailable: false with currency: null, and an attempt to start a top-up answers with code BOX_TOPUP_NOT_AVAILABLE.
FIX-0828-21: the auth key now gets the scopes the Bitrix24 account actually grants
Before
When an application's personal API key carried no Bitrix24 scopes, the platform had
no credential to ask Bitrix24 with, and the auth key was issued with crm +
placement only. An app that needed tasks, chat or disk hit silent permission
errors, and an issued key cannot be widened — only re-issued.
After
Scopes are probed over the channel the platform manages the account with, so the set no longer depends on whether a particular application key has a webhook. The auth key receives the intersection of the account's available scopes with the supported ones, as designed. When there is nothing to ask with, behaviour is unchanged and issuance is not blocked.
FIX-0828-22: an employee photo can be written through the API again
Before
The personalPhoto field is declared a string, so any array or object in it was refused with
400 INVALID_PARAMS — and the [file name, base64] array is the only shape Bitrix24 accepts.
On the entity routes no shape could write the photo: a base64 string and a URL answered 422,
batch routes 400. It only travelled through the POST /v1/users/invite wrapper.
After
On write, personalPhoto takes the file inline: an array of exactly two non-empty strings —
the file name and its base64 content — on POST /v1/users and PATCH /v1/users/:id. Reading
the field is unchanged, it still returns the photo URL. Other shapes stay refused on purpose:
the nested {"personalPhoto": {"fileData": [...]}} is accepted by Bitrix24 with success yet
clears the photo. Batch routes refuse the photo: a batch sub-call travels as a query string under the
global body cap, and past its length limit the value would be truncated while still
answering success. The POST /v1/users/invite wrapper accepts the same shape — it translates
the field name and validates the pair separately — but its body limit is still the global one
(1 MiB against the 40 MiB of the single routes), so send a real photo through
POST /v1/users or PATCH /v1/users/:id.