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

API changes: September 22, 2026

← Changelog · September 2026

BC-0922-1: single-entity batch paginates: a sub-call offset is finally read

Old format supported until: not provided

Before

A list sub-call of POST /v1/{entity}/batch ignored offset: the parameter was never read out of params, never reached Bitrix24 and was never applied as a client-side window. Two sub-calls differing only in the offset came back HTTP 200 with the same records, so paging through a single-entity batch was impossible — while the global POST /v1/batch and the single GET /v1/{entity} and POST /v1/{entity}/search all read it. For entities whose Bitrix24 method supports no navigation that sub-call also answered HTTP 200 — the first portion of records at any offset, that is, silently not the window the client asked for.

After

The sub-call reads offset and answers with an honest [offset, offset + limit) window, through the same resolution the global door uses: the offset is aligned to the Bitrix24 page, the remainder is trimmed on our side, and for entities whose method applies no navigation (statuses, deal-categories, bizproc-activities, bizproc-robots) — as well as those returning the whole set in one response — the window is cut over the complete collection. A sub-call result now carries hasMore next to total, and a degenerate window arrives as an OFFSET_BEYOND_FETCHED_PAGE warning in the meta of that same sub-call.

There is one breaking part: an entity whose Bitrix24 method cannot honour an offset (today that is telephony-lines) answers a sub-call with an offset above zero with an UNSUPPORTED_OFFSET error instead of the former HTTP 200 and first portion of records. The refusal is per sub-call — the neighbours in the batch still run, and the batch request itself still answers HTTP 200. Along with the offset the sub-call now honours limit as well: a page Bitrix24 returned longer than asked is cut to limit (50 by default), as on every other door. Such a sub-call used to hand back everything Bitrix24 sent — with windows that would mean pages of different offsets overlap and a client reads the same records twice.

What integrators should do

  1. Drop offset from sub-calls to telephony-lines: an offset is unavailable for that entity, and the UNSUPPORTED_OFFSET response says so directly. Narrow the selection with a filter instead.
  2. If your code relied on a page longer than requested — pass the number you need in limit explicitly, up to 5000 records.
  3. Page by the hasMore of the sub-call result rather than by arithmetic over total.

NEW-0922-2: two new refusal codes for agent runtime keys

An agent runtime key now receives two refusal codes that did not exist before. While an agent turn is waiting for user confirmation, POST /v1/chat/completions answers 409 with the code agent_turn_awaiting_confirmation: no model calls are made for that turn, so waiting for a human answer no longer consumes the limit. The code clears as soon as the user confirms or cancels the operation, and also once the waiting period expires.

A DELETE call to V1 from the same key that matches the previous one by method and address within a 30 second window answers 409 with the code AGENT_WRITE_ALREADY_EXECUTED. Such a call never reaches the Bitrix24 account, while error.firstStatus carries the response code of the first delete and error.firstExecutedAt carries its time. A failed first delete does not lock the repeat: a retry goes through as usual. Create and update are not deduplicated.

Keys that do not belong to an agent runtime are touched by neither code, and previously successful responses are unchanged.

NEW-0922-3: result delivery for a long-running solution method

A public route POST /v1/solution-calls/:callId/result is available: the solution posts the result of an asynchronous contract call (solution.invoke with mode: async) there. The address, a single-use reply token and the deadline reach the solution together with the call itself in the X-Vibe-Reply-Url, X-Vibe-Reply-Token and X-Vibe-Reply-Deadline headers; the platform sends the call with Prefer: respond-async, so the solution either answers 202 and posts the result here or answers 200 with the same body right away. Authentication is Authorization: Bearer vcr_…; no platform API key is accepted on this route. The body is {"outcome":"completed","result":{…}} or {"outcome":"failed","error":{"code","message","problem"}}; the answer is 200 with data.state. Errors: 401 REPLY_TOKEN_INVALID (the token matches no open call; whether the callId exists is not disclosed), 409 SOLUTION_CALL_CLOSED (the call is already closed, error.state carries its state), 422 RESULT_INVALID (body over 512 KiB, not JSON of the expected form, or result off the schema of the contract snapshot the call was accepted under). A 422 closes the call as failed — retrying with the same body is pointless. The platform delivers the accepted outcome to the Bitrix24 account itself. Existing calls and answers are unchanged: before this release mode: async answered FEATURE_DISABLED, and on Bitrix24 accounts where the capability is not enabled the answer stays the same.

FIX-0922-4: a Bitrix24 «parameter not supplied» refusal answers 400, not 422

Before

When Bitrix24 refused a call before the method ran — because the call did not carry a parameter the method declares as required — Bitrix24 answered Could not find value for parameter {…} and the Vibecode platform passed that out as 422 BITRIX_ERROR, that is, as a business error of the Bitrix24 account. The name of the missing parameter lived in the message text alone, there was no hint, and repeating the same request returned the same refusal — on the requisite list (GET /v1/requisites) 92 calls repeated that way across five accounts in three weeks.

After

The same refusal arrives as 400 INVALID_PARAMS — the code the other parameter rejections from Bitrix24 already use — and error.hint names the parameter whose value was missing and says that repeating the request unchanged will not help. The answer to any request Bitrix24 accepts is unchanged.

Impact on integrators

A client that told this case apart by 422 will now see 400 INVALID_PARAMS. The answer still carries the Bitrix24 text in error.message, and the parameter name is now also in error.hint. Refusals raised by the platform's own checks before it calls the account keep answering 400 MISSING_REQUIRED_PARAMS.

FIX-0922-5: Bitrix24 account address change: the call answers 409 `PORTAL_ADDRESS_CHANGED` instead of 500

Before

After a Bitrix24 account address change, the webhook behind an issued key stayed bound to the previous address. The platform refused such a call before sending it — a secret is never sent to an address the account no longer lives at — but the refusal was unnamed: every proxied request answered 500 INTERNAL_ERROR with no code and no hint, and inside a batch envelope the sub-call fell into the generic CALL_FAILED bucket. The response told the client neither the cause nor the way to recover.

After

The refusal names both the cause and the action: 409 with code PORTAL_ADDRESS_CHANGED, and the hint points at reconnecting the key — the Reconnect button in the Keys section; the key string and any bots linked to it are preserved. The same code with the same hint travels in the batch envelope sub-error. The refusal carries no retry deadline on purpose: retrying does not help until the key webhook is re-issued.

FIX-0922-6: deleting an app releases the authorization key held by its card

Before

DELETE /v1/apps/{id} removed the app and revoked its paired keys, yet the application card still counted the authorization key as issued. Issuing a new one was impossible — the issue endpoint answered 409 APPLICATION_HAS_AUTH_KEY and the re-issue endpoint answered 409 APPLICATION_NO_AUTH_KEY. For the same reason POST /v1/cowork/applications/{id}/key returned warningCodes containing APPLICATION_HAS_AUTH_KEY for an application that no longer had an authorization key.

After

The delete now releases that link as well: afterwards the application counts as having no authorization key, the false APPLICATION_HAS_AUTH_KEY warning for it is gone, and issuing a new key succeeds. The successful response of the delete itself is unchanged — it is still HTTP 204.

FIX-0922-7: the OpenAPI aggregation schema now describes fields the way the API accepts them

This corrects the DESCRIPTION published by GET /v1/openapi.json; the behaviour of POST /v1/{entity}/aggregate is unchanged and its responses are the same.

Before

The aggregate[].field property was published as a closed list of "*" plus the entity's grouping fields. Grouping fields and the fields sum/avg/min/max can be computed over are different sets, so a client validating the request body against the spec withheld calls the API executes: aggregation over a number-typed field of the entity outside the grouping list (on deals that is probability, taxValue, receivedAmount and others) and over a user field of the account typed integer, double or money. At the same time the spec admitted pairs the API always answers 400 INVALID_PARAMS to: count over a named field, and a numeric function over "*" or an empty name. The groupBy property was published as the same closed list and rejected the user fields the API accepts.

After

aggregate[].field is described together with function: count is computed over "*", while sum/avg/min/max take the name of a numeric field — either declared by the entity or a user field of the account. The entity's number-typed fields are listed in examples as a hint rather than a constraint: the set of user fields depends on the account and cannot be published in a shared spec, and the current list is served by GET /v1/{entity}/fields. The pairs the API always refused are now refused by the schema as well — including aggregation over a field the entity declares with a non-numeric type: such a name is rejected by the API regardless of what the account has configured. groupBy admits user fields and rejects names colliding with the response keys (count, aggregates, meta, groups), which the API never accepted. The operation's prose description no longer passes the grouping list off as the list of aggregation fields: it now names the two roles separately.

FIX-0922-8: default per-user limits raised — keys, servers, bots

Before

On a Bitrix24 account where these limits were never set by hand, the per-user defaults were 10 API keys, 5 servers and 5 bots.

After

The defaults are raised to 100 keys, 50 servers and 50 bots per user. This affects only Bitrix24 accounts that never set their own values; an account with its own configuration keeps it unchanged. Existing requests keep working — there is simply more headroom.

BC-0922-9: Retrying an unfinished key rotation requires recovery

Old format supported until: not provided

Before

After an incomplete key-replacement rollback, a request with a new Idempotency-Key could answer 201 for an orphan candidate although revoking the source key's tokens had not been confirmed.

After

Another rotation of either the source or replacement key answers 409 KEY_ROTATION_RECOVERY_REQUIRED. Replacing an application key or assigning such a key to a server answers 409 APPLICATION_KEY_RECOVERY_REQUIRED. A new Idempotency-Key does not bypass the refusal. Minting an access token during an unfinished rotation also answers 409 KEY_ROTATION_RECOVERY_REQUIRED; if the server has already changed its key, it answers 403 SERVER_KEY_MISMATCH, without a new token. Transferring a bot with a source or target key in unfinished rotation answers 400 TARGET_KEY_INVALID with error.reason: recovery_required, without changing ownership. A concurrent transfer for the same Bitrix24 account answers the existing 409 BOT_TRANSFER_CONFLICT instead of an opaque 500, without changing ownership.

Affected endpoints: POST /v1/keys/{id}/rotate, DELETE /v1/keys/{id}, POST /v1/cowork/applications/{id}/key, POST /v1/infra/servers, POST /v1/infra/servers/{id}/access-tokens, POST /v1/bots/{botId}/transfer.

Unconfirmed revocation answers 500 KEY_ROTATE_REVOKE_FAILED without a raw secret. The candidate is named in error.details.orphanKeyId, and the rollback remainder in error.details.strandedSlots. A nonempty remainder requires support. An absent or empty remainder does not permit retry by itself. An unreferenced candidate can only be removed through the normal DELETE endpoint after the API confirms that no other resource still references it.

Impact on integrators

Do not retry with a new idempotency key. Complete recovery through support or verify normal deletion of the empty candidate, then re-read the current key and server state before a new request. The previous behavior is no longer supported.

FIX-0922-10: JavaScript service names no longer replace a parameter value

Before

Names every JavaScript object carries — constructor, toString, valueOf, hasOwnProperty — counted as found when a value was checked against a list of allowed ones. The value turned out to be a service function, and the behaviour then differed per endpoint:

  • GET /v1/userfields/constructor returned a Bitrix24 error instead of the 400 UNKNOWN_ENTITY refusal;
  • GET /v1/requisite-links?filter[constructor]=… and ?sort=constructor sent a garbage field name to Bitrix24 instead of 400 UNKNOWN_FILTER_FIELD / 400 UNKNOWN_SORT_FIELD;
  • GET /v1/openline-configs?filter[constructor]=… and ?sort=constructor sent a garbage field name instead of the expected CONSTRUCTOR; this endpoint has no refusal for an unknown field and never had one;
  • POST/PATCH /v1/userfields/:entity sent such a field to Bitrix24 under a substituted name instead of passing it through; the same for the inner keys of list items;
  • POST /v1/users/invite used the same substituted name instead of passing it through;
  • model: "constructor" in AI requests replaced the model name with a service value.

After

Allowed values are checked against own keys, so service names are not found among them. Where an endpoint refuses an unknown value, that refusal now comes before the Bitrix24 call; where there is no refusal, the field name is passed through as is.

NEW-0922-11: Cowork/Code desktop ticket for phone access

The new POST /v1/cowork/relay-ticket method issues the Cowork/Code desktop app a short-lived signed ticket that the phone-access relay requires before it lets the desktop in. The body carries pubkey, the desktop's Ed25519 public key (32 bytes, unpadded base64url). A 200 response contains ticket (an EdDSA JWT valid for 5 minutes) and expiresAt. The method is available only to a desktop app key with the vibe:cowork scope and only while the Cowork/Code seat is active.

Refusal codes: 403 INSUFFICIENT_SCOPE (no vibe:cowork scope), 403 COWORK_DESKTOP_KEY_REQUIRED (not a desktop app key), 404 COWORK_NOT_ACTIVATED (no seat), 403 COWORK_SEAT_INACTIVE (the seat is not active), 400 INVALID_PUBKEY (malformed key), 429 (at most 30 tickets per hour per person), 503 COWORK_FEATURE_DISABLED and 503 COWORK_RELAY_NOT_CONFIGURED (ticket issuance is temporarily unavailable).

FIX-0922-12: the owner-only policy now behaves the same on every entry point

Before

Under the OWNER_ONLY policy, a user listed in the access list could open the application through the placement inside Bitrix24, yet was refused on the direct link. The documentation states that access-list entries do not apply under OWNER_ONLY: the «restore privacy» scenario — switch the server back to OWNER_ONLY and leave the entries in place — therefore did not close access for everyone.

After

Every path now applies one rule: under OWNER_ONLY only the owner opens the application, and access-list entries do not apply, exactly as documented. To open access to specific people again, switch the policy to NAMED_USERS — the entries are already stored and take effect immediately.

Integrator impact

If your scenario relied on a listed user entering through the placement under OWNER_ONLY, switch the server to NAMED_USERS: the set of people stays the same.

FIX-0922-13: a wake schedule can be set for an app inside a galaxy again

Before

An app inside a galaxy — an AI agent included — could not be given a schedule window: POST /v1/infra/servers/{id}/wake-schedules answered 400 ALWAYS_ON_CONFLICT, as if the owner had paid to keep the server online around the clock. Nobody made that choice: the app inherits its plan from the galaxy, and a galaxy only accepts non-interruptible plans, so the «runs 24/7» condition was met on its own. Combined with idle sleep being unavailable to an agent, the owner had no way to stop the work overnight — while being billed around the clock.

After

A plan inherited from the galaxy no longer counts as choosing the around-the-clock mode: an app inside a galaxy takes schedule windows the usual way. A standalone server whose owner did pick a non-interruptible plan and turned sleep off behaves as before — 400 ALWAYS_ON_CONFLICT.

Integrator impact

No action required. A request that used to be rejected for an app inside a galaxy now creates a window, and GET /v1/infra/servers/{id}/logs on a sleeping app no longer reports the window as refused.

NEW-0922-14: three more presets in the work-schedule library

GET /v1/work-schedules returns three additional presets — weekdays-9-19, weekdays-9-20 and weekdays-8-19 (weekdays, Monday to Friday). There used to be three presets, and when an account's working week did not match any of them, the only option was a custom schedule via POST /v1/work-schedules — even if the difference was a single hour.

The new presets are created in the account on the first read of the library, just like the earlier ones, and they are just as read-only: a preset carries canEdit: false, and an edit attempt answers WORK_SCHEDULE_PRESET_READONLY. The platform will not assign them to any machine on its own — they only show up in the list.

The earlier keys and their windows are unchanged, and machines already assigned kept their schedules.

The set of presets may keep growing, so treat an unknown presetKey as a preset you have no local label for rather than an error. Identify presets by presetKey, not by name: the name depends on the language of the key.