For AI agents: markdown of this page — /docs-content-en/changelog/2026-08-27.md documentation index — /llms.txt
API changes: August 27, 2026
NEW-0827-1: aggregation marks a partial total next to the number itself
Before
POST /v1/{entity}/aggregate with sum / avg / min / max computes over the first 5000 records matching the filter. The incompleteness marker lived in data.meta.truncated alone, while data.aggregates.amount looked like { "sum": 1234567 } — a plain number with nothing attached. A client reading only the total received an understated result under code 200 with nothing to branch on: data.count meanwhile reported the full number of records.
After
When the selection is truncated, every field object in data.aggregates and in groups[].aggregates additionally carries truncated: true — { "sum": 1234567, "truncated": true }. The same condition adds a warning with code AGGREGATE_TRUNCATED to data.meta.warnings. The sum / avg / min / max values stay numbers, nothing wraps them into an object. On a complete selection the response does not change at all: the field object carries no truncated key and no meta.warnings appears. The 5000 ceiling and data.count behave as before.
Grouping gains one more field — groups[].truncated: on a truncated answer the group object itself carries this marker next to its counter. The marker says the answer is a sample; whether the group counter is exact is shown by meta.aggregatePath — on the ordinary walk groups[].count is computed over the slice that was read, while under the stage-count mode (meta.aggregatePath: "fanout") it comes from a separate probe and is exact. On a count-only grouping it is the only marker next to a number: a count expression produces no field object, and both aggregates bags come back empty. data.count and data.meta.totalRecords stay exact at any size and never carry it.
Alongside the incompleteness marker the response now always reports the size of the gap. Previously meta.recordsShortfall arrived only when the truncation was NOT caused by the ceiling: on a selection above 5000 records the response raised truncated: true with no gap-size field at all. That case is the most common one: on a pipeline of 20,000 deals a client saw "this answer is a sample" and zero as the size of what was missing. Now every reason for truncation carries a quantifier next to the marker — meta.recordsShortfall (how many records never reached the numbers) or meta.pageErrorSample (when the slice was cut short by a sub-page error). On a complete selection neither key appears in the response, as before.
A false alarm is removed at the same time. The truncation marker used to be raised from the comparison "more than 5000 records in total", which is only an indirect sign that the read will be cut. On entities where the limit never reaches Bitrix24 — pages and sites (landing.*), Open Channels configs — nothing is cut and every row is read. Such a response still declared itself a sample. The marker is now raised from the fact: fewer rows read than promised, then and only then. A fully read selection of any size counts as complete again.
This also closes a gap in grouping deals by stage: when a stage walk returned fewer records than its probe had counted (usually because of access rights), the response used to arrive with data.meta.truncated: false and looked complete. Such a response now raises truncated honestly — and with it comes both the marker next to the number and the warning.
BC-0827-2: runtime installation order and nginx startup have changed
Old format supported until: not provided
Before
During a redeploy, the runtime was installed after the previous app version had been stopped. A failed installation could leave the app stopped. For static, php83, and php83-mysql, package installation could implicitly start and enable the system nginx.service, and a request could rely on that process.
After
POST /v1/infra/servers/:id/deploy installs the runtime before stopping the app and replacing its files. If installation fails, the previous app is not stopped. Installing nginx for static, php83, and php83-mysql does not start the system nginx.service or leave it newly enabled. The existing service enablement state is preserved.
What integrators should do
For static, php83, and php83-mysql, make sure the command in the start field starts the process that listens on the application port inside app.service. Do not rely on an implicit start of the system nginx.service. Requests using other runtimes do not need to change.
NEW-0827-3: unfinished BOX account reason and employee identity transfer between accounts
The BOX account card with an unfinished connection (accessPending: true) in the GET /api/portals response now carries a reason in the optional accessPendingReason field: transfer_requested — the employee already has a pending request to move their link to this Bitrix24 account from another Vibecode account, incomplete — the connection is simply unfinished. Such requests are managed by the new /api/box-transfers/* routes: the current holder lists their pending requests, opens one via the email link, and approves or declines the transfer.
FIX-0827-4: unfinished BOX account connection is visible in the list
Before
GET /api/portals silently hid a BOX account whose connection was unfinished.
After
The account card is returned with the new accessPending: true field.
FIX-0827-5: user invitations preserve all profile fields
Before
POST /v1/users/invite accepted ten writable profile fields in the regular Vibecode API format but forwarded them without converting their names. The user was created while the photo, external ID, time zone, and personal details remained empty.
After
POST /v1/users/invite converts these fields to the Bitrix24 format. Clients do not need to change their requests, and the previous Bitrix24 field format keeps working.
FIX-0827-6: single comment lookup finds a comment with a string identifier
Before
GET /v1/tasks/:taskId/comments/:id could return 404 NOT_FOUND when Bitrix24 returned the found message identifier as a string. The same comment was still present in the task comment list.
After
The endpoint normalizes the numeric identifier from the Bitrix24 response and returns the found comment with numeric id and authorId values.
Impact on integrations
No client changes are required.
FIX-0827-7: contact select accepts typed email addresses again
Before
POST /v1/contacts/search and a contact sub-call in POST /v1/batch rejected emailWork, emailHome, and emailMailing with UNKNOWN_SELECT_FIELD, even though contact responses already contained these values. GET /v1/contacts/fields did not list these names.
After
Both operations accept the three names in select and return a successful response with the selected values. GET /v1/contacts/fields describes them as nullable read-only fields.
FIX-0827-8: tariff headers are returned for infrastructure requests
Before
Responses from the /v1/infra/* endpoint family did not contain X-Tariff-Checked-At or X-Tariff-Is-Commercial, even when the tariff check had completed successfully.
After
Responses from /v1/infra/* contain tariff headers under the same rules as /v1/me: the time of the last successful check and the commercial-tariff indicator are returned when the corresponding account data is available. The response status and body are unchanged.
Impact on integrators
No request changes are needed. A missing header once again means that the corresponding tariff-check data is unavailable, rather than that the infrastructure route was skipped.
NEW-0827-9: array element schemas are available in field contracts
GET /v1/orders/fields, GET /v1/basket-items/fields, and GET /v1/guide now return itemSchema for arrays with a declared element shape. The schema recursively describes type, readonly, nullable, properties, and nested itemSchema values. Clients can optionally read the new field, and existing integrations continue to work without changes. The separate items key continues to contain the raw Bitrix24 value directory for enumeration fields.
FIX-0827-10: lists without a total no longer look complete too early
Before
If Bitrix24 did not return the total record count, a list request with a limit above 50 stopped after the first page. A full window of up to 50 records could also look like the last page because its size was treated as the collection size.
After
The platform continues reading after each full page and stops at a short page or the request limit. When the collection end is not yet proven, the total remains unknown and meta.hasMore or truncated reports that more records may exist.
Impact on integrations
No request changes are required. Requested windows larger than one page are no longer silently truncated; GET /v1/mail/messages now always includes a boolean truncated signal and omits an unknown total instead of returning null.
Affected endpoints: GET /v1/humanresources/nodes, GET /v1/mail/mailboxes, GET /v1/mail/messages.
NEW-0827-11: the self-description now points at the Cowork/Code promo code docs
The GET /v1/guide and GET /v1/me responses now name the desktop application's promo code pair. The cowork section of guide.ts carries the new docs.couponPreview and docs.couponRedeem pointers to the POST /v1/cowork/coupon/preview and POST /v1/cowork/coupon/redeem pages, and the /v1/me rules gained an item about them: which key class is required, how the check differs from the redemption, and why accessGranted: false deserves a screen of its own.
The endpoints themselves behave as before — what is new is that an integrator or an agent finds their description without reading the changelog.
FIX-0827-12: the Cowork/Code rate limits no longer quote a number the client never receives
Before
The GET /v1/guide reference, the GET /v1/me rules and the Cowork/Code documentation pages quoted rate limits as a concrete number: 20 requests per minute for POST /v1/cowork/coupon/preview, 10 for POST /v1/cowork/coupon/redeem, 30 for GET /v1/cowork/subscription/preview, 5 for DELETE /v1/cowork/key, 3 per five minutes for POST /v1/cowork/deploy-key, 30 for GET /v1/cowork/applications/defaults and 6 for POST /v1/cowork/applications. None of those numbers ever reached the client: the cap is divided across the platform's processes, and the x-ratelimit-limit header returned 7, 4, 10, 2, 1, 10 and 2 respectively. For deploy-key, key and the create-application wizard the two channels of one endpoint contradicted each other — the page promised one thing while the header returned another.
After
The GET /v1/guide reference, the GET /v1/me rules and the section's documentation pages now name the x-ratelimit-limit response header as the source of the current value and ask you not to hard-code a number in client code. The endpoints themselves did not change and the response is unchanged — what was corrected is the description that diverged from it. A client that already read the header changes nothing.
What this entry does NOT cover. The machine schema GET /v1/openapi.json still quotes numbers for five operations of the section — subscription/preview, deploy-key, applications/defaults, applications and key — and those numbers diverge from the header exactly as the others did. A client generated from the schema must still read x-ratelimit-limit rather than the value in the operation description. One promise in the schema is accurate: the "three requests per hour" on POST /v1/cowork/activate-market-trial, where the cap is multiplied by the process count before the division.
FIX-0827-13: telephony line creation returns a usable key
Before
POST /v1/telephony-lines returned an internal numeric identifier. It could not be used in the path for updating or deleting the created line.
After
The data.id field contains the created line number. This value can be used in subsequent update and delete requests.
Impact on integrations
No action is required. New create responses immediately contain an addressable key.
NEW-0827-14: time entry lists explicitly reject unsupported filter envelopes
Before
An unsupported filter in GET /v1/task-time and GET /v1/tasks/:taskId/time could return status 200 with a broader result set than the client expected.
After
A non-empty, bracket, JSON, or repeated filter returns 400 UNSUPPORTED_FILTER. For requests with the named userId, taskId, from, and to parameters, the response remains HTTP 200 and their behavior is unchanged.
FIX-0827-15: PUBLIC app robots.txt is available to crawlers
Before
Exact GET and HEAD requests to /robots.txt always received the gateway's local denial, even when the app had accessPolicy=PUBLIC, was running, and served its own file.
After
With accessPolicy=PUBLIC and a live tunnel, exact GET and HEAD requests to /robots.txt return the app response: 200 text/plain with a complete non-empty GET body no larger than 1 MiB (HEAD has no body), or 304. For every other policy, an unavailable app, or any invalid response, the gateway still returns local 200 text/plain with Disallow: /.
Impact on integrators
A PUBLIC app can now control indexing through its own /robots.txt file. No change is needed for non-public apps: denial remains fail-closed by default.
FIX-0827-16: entity filters are no longer lost on list calls
Before
list_entities could send filters as ordinary query parameters. This made a field named sort collide with the sorting directive, while openline-configs could return the full list instead of a filtered result. On envelope-based entities, a filter could also be lost in GET /v1/{entity}/aggregate, POST /v1/batch, and POST /v1/{entity}/batch.
After
list_entities sends fields through filter[...], separately from sorting. Every V1 list path applies the envelope declared by the entity, so a non-empty filter reaches Bitrix24 and a non-matching filter returns an empty result instead of the full collection. The custom bookings list accepts the same bracket form for its required dateFrom/dateTo window while preserving the existing flat form.
Impact on integrations
No request changes are required. Previously over-broad successful responses now match the supplied filter.
FIX-0827-17: the application access list contains Bitrix24 account employees only
Before
GET /v1/infra/servers/{id}/access returned every audience row in users[]. The element's id field is declared required, yet a grant issued to someone outside the server's Bitrix24 account has no account number at all — such an element would arrive with an empty id, breaking any client that reads the field as required.
After
users[] contains only rows carrying an account number. Rows addressed by a network identifier are excluded from this list — they live in a different identifier space, and the two must never be mixed. On today's data the response is unchanged: no such rows exist yet. The same rule applies to the matching dashboard screen.
FIX-0827-18: the `connect` step of repair status no longer fails for an agent that did connect
Before
Server repair counted the connect step as complete only when the version of the agent that came
up matched the version set in platform settings. If the agent connected on a different version, the
tunnel was up and blackholeStatus read CONNECTED, yet
GET /v1/infra/servers/{id}/repair-status returned status: failed, step: connect and
error: "Agent did not connect".
After
The connect step completes once the agent connects to the Gateway — exactly as the step order is
described on the operation page. The installed agent version is still returned in
data.agentVersion and no longer affects the repair outcome.