For AI agents: markdown of this page — /docs-content-en/changelog/2026-08-28.md documentation index — /llms.txt

API changes: August 28, 2026

← Changelog · August 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.

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.

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.