# API changes: September 24, 2026

[← Changelog](/docs/changelog) · [September 2026](/docs/changelog/2026-09)

### NEW-0924-2: Multiplicity flag for Bitrix24 account fields in the entity fields description

In the `GET /v1/{entity}/fields` response (for example, `GET /v1/deals/fields`), a field that comes from the Bitrix24 account now carries `multiple: true` when Bitrix24 reports that the field accepts several values — for example, a multiple custom field `ufCrm_*`. Send the value of such a field as an array when creating or updating a record. The absence of `multiple` does not mean the field is single-valued: the flag is not emitted for fields the platform describes itself, or for fields whose multiplicity the Bitrix24 account did not report.

### BC-0924-3: CRM document sorting takes one bracket level only

> Old format supported until: not provided

**Before**

[GET /v1/crm-documents](/docs/entities/documents/crm-list) accepted a second bracket level under the sorting field name. The spellings `order[updateTime][]=desc` and `order[updateTime][0]=desc` answered HTTP 200 and sorted the listing as if `order[updateTime]=desc` had been supplied — a sort the caller never wrote. The spelling `order[updateTime][x]=desc` answered HTTP 500.

**After**

A sorting entry must carry a direction, not a structure. A second bracket level under the field name — `order[updateTime][x]=desc`, `order[updateTime][]=desc`, `order[updateTime][0]=desc` — returns HTTP 400 `INVALID_ORDER` before Bitrix24 is called. The single-level spelling `order[updateTime]=desc` and the direction case work as before. An unknown direction is still dropped rather than refused, and a spelling the parser cannot place at all — `order=desc`, `order[]=desc`, a third bracket level — still leaves the listing unsorted.

**What integrators need to do**

Supply sorting with one bracket level: `order[updateTime]=desc`. If the query string is assembled from an array or an indexed list, convert it to that shape: the former spellings `order[updateTime][]=desc` and `order[updateTime][0]=desc` are now refused instead of sorted.

### FIX-0924-4: Cowork ERP management status no longer asks for a confirmation code by default

**Before**

With platform two-factor protection on, `GET /v1/cowork/onec/status` returned `admin.stepUp.required=true` and a non-empty `channels` list to a Bitrix24 account administrator.

**After**

By default `GET /v1/cowork/onec/status` returns `admin.stepUp={required:false,state:"NOT_REQUIRED",expiresAt:null,freshUntil:null,channels:[]}` and does not offer the `REQUEST_STEP_UP` action. The response remains `200`. The contract revision `onec-cowork-management/v1-proposed-2026-09-21-r3` and the response shape are unchanged.

### FIX-0924-5: the daily call allowance is counted by completed hours

**Before**

The `X-RateLimit-Used` and `X-RateLimit-Remaining` headers were described as including calls from the current hour, accurate to the minute.

**After**

Both headers count today's usage by completed hours: calls from the current hour are included once that hour ends, so the headers trail reality by up to 70 minutes and early in an hour may not reflect calls already made. The daily allowance size, the `X-RateLimit-Quota` header and the `QUOTA_EXCEEDED` refusal code are unchanged, and a call within the allowance still answers HTTP 200. Clients need no changes; if you need the exact remainder, account for your own current-hour calls on your side.

### FIX-0924-6: the aggregation reference in GET /v1/guide describes POST aggregation and numeric-function fields by type

**Before**

The `operations.aggregate` block in [GET /v1/guide](/docs/keys-auth/guide) described the deprecated `GET …/aggregate` with the `op` and `field` parameters and named `aggregatableFields` as the source of the field for `sum`/`avg`/`min`/`max`. For smart-process items (`/v1/items/:entityTypeId`) it pointed at `GET /v1/items/:entityTypeId/aggregate`, which does not exist. The `400 INVALID_PARAMS` message about an unknown field in `POST …/aggregate` listed the same `aggregatableFields` instead of the numeric fields.

**After**

`operations.aggregate` describes `POST …/aggregate` with the `aggregate[]` array. The field for `sum`/`avg`/`min`/`max` is any field of type `number` from `…/fields`, or a numeric user field where the entity supports them (the `ufSupport` key is present). `aggregatableFields` lists the fields for `groupBy`. The `400 INVALID_PARAMS` message lists the numeric fields of the entity, and for an entity without numeric fields it says `This entity declares no number-typed field.`. Where an entity has no user fields, the `ufSupport` key is gone, and neither the reference nor `openapi.json` promises them. The `GET /v1/guide` response remains HTTP 200, and the aggregation error code and status are unchanged.

**Impact on integrators**

If a client read `params.op` and `params.field` from the reference, switch to `params.aggregate` and call `POST …/aggregate`. Calls to the deprecated `GET …/aggregate` keep working as before.

### NEW-0924-7: a default schedule for new machines

An administrator of the Bitrix24 account can mark the work schedule that new machines get by default: [PUT /v1/work-schedules/:id/default](/docs/infra/work-schedules/set-default) with the body `{"audience": "machines", "enabled": true}`. There are two marks: `machines` covers servers and galaxy applications, `agents` covers agents and bots. The second one is separate, because an agent on a schedule sleeps outside its windows and answers no messages until the next window opens. Each audience has at most one marked schedule: a new mark moves over from the previous one, and `"enabled": false` removes it.

A server created through [POST /v1/infra/servers](/docs/infra/servers/create) without `runMode` gets the marked schedule. An explicit `runMode`, `IDLE` included, still wins over the mark, and machines that already exist keep their mode.

Every row of the library [GET /v1/work-schedules](/docs/infra/work-schedules/list) now carries `defaultForNewMachines` and `defaultForNewAgents`. Only an administrator can change the mark, everyone else gets `403 ADMIN_ONLY`.

### FIX-0924-8: the run mode named at create now reaches a galaxy application

**Before**

[POST /v1/infra/servers](/docs/infra/servers/create) accepted the `runMode` block, but when the machine was placed in a galaxy the block was silently dropped: the application was created in the default mode. A schedule missing from the account was not refused in that case either.

**After**

The `runMode` block applies to a galaxy application too. A missing or empty schedule is refused before the create, exactly as for a standalone server: `400 WORK_SCHEDULE_NOT_FOUND` or `400 WORK_SCHEDULE_EMPTY`, and no application is created.

**Impact on integrators**

An integration that passes `runMode` gets the named mode without a separate `PATCH /v1/infra/servers/:id/run-mode` call. A missing schedule is now refused on galaxy placement too, as the documentation already promised.

### FIX-0924-9: aggregation reports a Bitrix24 account error the same way the list does

**Before**

`POST /v1/{entity}/aggregate` and the deprecated `GET /v1/{entity}/aggregate` returned a
Bitrix24 account refusal without the explanations tied to the called method: the error body carried
no `hint` with the known limitation of that method and no `warning` that the same error had
already repeated. The list and search endpoints of the same entity answered the same refusal
with both fields. One Bitrix24 account refusal therefore looked different to a client depending on
which address had been called.

**After**

Both aggregation endpoints report one cause with the same code and the same explanations as
`GET /v1/{entity}` and `POST /v1/{entity}/search`. The error body now carries a `hint` with
the known limitation of the called Bitrix24 method, and a `warning` when the refusal repeats.
Successful aggregation responses are unchanged, and their success status codes stay the same.

### NEW-0924-10: new refusal code `agent_turn_loop_detected` for a looping agent turn

[POST /v1/chat/completions](/docs/ai/chat/completions) gained the refusal code `agent_turn_loop_detected` (409) for a looping agent turn. **The refusal is not in effect yet**: such requests are served as before, and existing integrations keep working unchanged. Enabling the refusal will be announced in a separate BC entry with a date.

Refusal condition once enabled: `messages` carries 15 or more model answers with the `assistant` role and no `tool_calls` after the last message with the `user` role. Every such answer after the last `user` message is counted, not only consecutive ones: tool calls in between do not reset the count. Answers with `tool_calls` are not counted themselves, so a turn in which the model calls tools at every step does not meet the condition. No model call is made for such a request, so the loop does not consume the limit. To continue, send a new user message — the count starts over.

### FIX-0924-11: an empty `fields` in a bot request body no longer swallows the flat fields

**Before**

A body of `{"fields": [], "command": "help", "title": "Help"}` — how an empty dictionary serialises,
for instance via PHP `json_encode([])` — was taken for the Bitrix24 format and reached the Bitrix24 account as
`fields: []`. The `command` and `title` actually sent were dropped in silence, with no warning in the
response. The same on `PATCH /v1/bots/{botId}` and `PATCH /v1/bots/{botId}/chats/{dialogId}`, where
`null`, `false`, `0` and an empty string posed as an empty wrapper too: the request went to the Bitrix24 account
verbatim, the account answered `200`, and nothing changed.

**After**

An empty `fields` wrapper no longer counts as a wrapper — the body is read as flat and reaches
Bitrix24 whole. Affects `POST /v1/bots/{botId}/commands`, `PATCH /v1/bots/{botId}/commands/{commandId}`,
`PATCH /v1/bots/{botId}` and
`PATCH /v1/bots/{botId}/chats/{dialogId}`. A non-empty wrapper is still forwarded unchanged. Two
consequences: the `fields` key no longer shows up in `warning.droppedFields`, and an unrecognised
top-level key is dropped when the body is folded — it never reached Bitrix24 anyway.

### FIX-0924-14: A bot events request with an explicit offset below the stored one no longer moves the cursor

**Before**

[GET /v1/bots/{botId}/events](/docs/bots/events/polling) with an explicit `offset` below the stored position (for example, `offset=0` for debugging) moved the stored cursor whenever the response carried events. The next ordinary request without `offset` went out with the new position, Bitrix24 deleted the events handed to the debug request as acknowledged, and the bot's main loop never received them.

**After**

An explicit `offset` below the stored position returns events without moving the cursor, and the response carries `persisted: false`. An explicit `offset` equal to or above the stored position moves the cursor as before. Bitrix24 deletes acknowledged events, so `offset=0` shows only the events still unacknowledged in the queue, not the full history.

**Impact on integrators**

No action needed. A debug request with `offset=0` no longer takes events away from the main loop, and paging by `nextOffset` works as before.

### NEW-0924-15: resubscribe now reports the event mode Bitrix24 holds

A bot in `fetch` mode receives events only while Bitrix24 holds that same mode for it. That
value is the authoritative one and it can drift apart from the one the Vibecode platform
stores — and then [GET /v1/bots/{botId}/events](/docs/bots/events/polling) succeeds, reports
no error, and the queue stays empty indefinitely.

The [POST /v1/bots/{botId}/resubscribe](/docs/bots/management/resubscribe) response now
carries three new fields. `b24EventMode` is the mode Bitrix24 held BEFORE the call (`null`
when Bitrix24 did not report it). `diverged` says whether it differed from the `eventMode`
the platform stores. `hint` says what the call actually did and what to check next.

The distinction matters: Bitrix24 rebinds events only on a REAL mode change. When the modes
already matched, the call rebound nothing, and the former `{ resubscribed: true }` answer
read as a completed recovery. That case is now stated outright, and the hint points at the
causes upstream of the queue — chat membership, and whether a mention-only bot is actually
mentioned.

The hint returned by `GET /v1/bots/{botId}/events` after a run of empty polls was rewritten
from the same analysis: it labels `eventMode` as the platform's own value and sends you to
`resubscribe` for the authoritative one.

### NEW-0924-16: sign-in to a deployed app with a Cowork desktop key

The new endpoint [POST /v1/cowork/app-login](/docs/cowork/app-login) takes the address of a deployed app and returns the same address with an added `__gw_token` parameter and its lifetime `expiresIn`. The Cowork/Code built-in browser that opens this address signs in to the app as the key owner, with their Bitrix24 user ID, without a sign-in form. The token opens only this app. The endpoint accepts only a Cowork/Code desktop key with the `vibe:cowork` scope. An inexact app address is refused with `400 INVALID_APP_URL`, every access outcome with a single `404 APP_NOT_AVAILABLE`, and an unknown Bitrix24 user ID with `409 B24_USER_UNKNOWN`.

### BC-0924-17: list methods check the key before reading the body and answer 401 without one

> Old format supported until: not provided

**Before**

On every `/v1/lists/*` method the key was checked after the platform had read and parsed the request body. A request without a key or with an invalid key therefore got an answer about the body, not about the key: `400 INVALID_JSON_BODY` on JSON that failed to parse and `413 PAYLOAD_TOO_LARGE` on a body over 1 MB, for example on [POST /v1/lists/:iblockId/elements](/docs/lists/elements/create). The server read the whole body for any sender.

**After**

The key is checked first, before the body is read. A request without a key or with an invalid key on any `/v1/lists/*` method gets `401` whatever the size and content of the body, and the body is not read. This removes the ability to make the server read a large body without a key — the precondition for letting an element write accept a body of up to 40 MiB. Responses to requests with a valid key are unchanged.

**What integrators should do**

Nothing, if the key is sent. If your code tells apart responses to a request without a key or with an invalid key by the `400` or `413` codes, switch that check to `401`.

### FIX-0924-18: a file in a list element property is no longer capped at 1 MB

**Before**

The value of a "File" property of a list element travels in the request body as base64, and the element write body was capped at 1 MB. The practical ceiling for one file was about 750 KB: [POST /v1/lists/:iblockId/elements](/docs/lists/elements/create) and [PATCH /v1/lists/:iblockId/elements/:elementId](/docs/lists/elements/update) with a larger file answered `413 PAYLOAD_TOO_LARGE`, and nothing was written. The same file in a deal or contact field went through.

**After**

The body of a list element create and update is capped at 40 MiB, the same as CRM entity writes — just under 30 MiB of original file after base64. The other list methods and batch calls (`POST /v1/batch`) keep the previous 1 MB cap, so send a large file in a single call.

An element write with a body over 1 MB shares the limit on concurrently processed large bodies with CRM entity writes: when it is taken, the response is `429 LARGE_BODY_BACKEND_BUSY` with a `Retry-After: 5` header, the request was not executed, and it has to be retried.

**Impact on integrators**

No action required: a request that used to get `413` now goes through. The call to Bitrix24 is limited to 15 seconds and is not retried, so a file right at the limit on a slow account may get `BITRIX_TIMEOUT` — leave some headroom.
