For AI agents: markdown of this page — /docs-content-en/changelog/2026-09-03.md documentation index — /llms.txt
API changes: September 3, 2026
NEW-0903-1: `GET /v1/models` exposes the reasoning control declaration
Every model in GET /v1/models and GET /v1/models/{model} now carries a reasoning field: null when no declaration is set, otherwise an object { map, default, budgetTokens } — map translates a platform step (none|low|medium|high|max) into the model's native mode, default names the step the model applies when no reasoning parameter is sent, budgetTokens — reasoning token-budget support. The field is additive: the shape of capabilities is unchanged and existing requests keep working as before. Details — /docs/ai/models/list.
NEW-0903-2: reasoning control in chat completions
POST /v1/chat/completions accepts reasoning_effort, reasoning and chat_template_kwargs, normalizes them to the five Vibecode platform steps (none, low, medium, high, max) and applies the nearest step the model supports — rounding down and never disabling reasoning without an explicit request. The response carries a reasoning field (requested, applied, native), the REASONING_EFFORT_ADJUSTED, REASONING_NOT_SUPPORTED, REASONING_CANNOT_BE_DISABLED, REASONING_BUDGET_NOT_SUPPORTED, TEMPERATURE_OVERRIDDEN_BY_REASONING warnings in warnings and the X-Reasoning-Applied, X-Reasoning-Native, X-Reasoning-Warnings headers — in streaming mode the headers are the only channel. Without the parameter in the request the body sent to the model is unchanged and the model's behavior does not change. Reasoning tokens are billed as output tokens at the same rate. Details — /docs/ai/chat/completions.
FIX-0903-3: invalid reasoning parameter values are rejected
Before
Unknown reasoning_effort, reasoning, chat_template_kwargs fields were silently dropped, and a request with a typo in the value ran as if the parameter were absent — the response was HTTP 200.
After
A value outside the vocabulary (none, minimal, low, medium, high, xhigh, max), a non-positive reasoning.max_tokens or a non-object chat_template_kwargs is rejected with 400 invalid_request in the OpenAI envelope { "error": { "message", "type", "code" } }; an explicit null in any of the three fields is accepted as "not set"; for a valid request the response remains HTTP 200.
FIX-0903-4: GET /v1/me no longer declares region as required
Before
In the GET /v1/me response, the server-create description for requests without source marked region as required and included it in the required-fields list. As a result, clients using self-discovery could require a region even though the server-create contract already allowed it to be omitted.
After
GET /v1/me marks region as optional and explains that the platform uses the selected provider's default region when it is omitted. Only self-discovery changed: GET /v1/me remains HTTP 200, while the existing behavior and HTTP 201 response of POST /v1/infra/servers without region are unchanged.
Integrator impact
Clients that build requests from GET /v1/me may stop treating region as required. Existing requests that explicitly supply a region need no changes.
FIX-0903-5: direct BOX calls honor certificate trust
Before
On a BOX portal with a trusted self-signed certificate, windowed search, file downloads, and some key operations could fail with a TLS error while regular API calls worked.
After
All these calls use the trust setting only for the exact BOX portal address. Cloud and OAuth calls, and download redirects to an external address, keep strict certificate verification.
BC-0903-6: an app key without an employee session reads only app-shared files by name
Old format supported until: not provided
Before
GET /v1/storage/objects/{key} made with an app key and no Authorization header returned an
employee file when exactly one object existed under that logical name. It worked only because the name
was one per app, and the behaviour could not be relied upon: as soon as more than one object held the
name, the choice became arbitrary.
After
Such a request reads only the app-shared file, from the release onwards — there is no transitional mode. If the name is held solely by per-employee files, the
response is 404 STORAGE_OBJECT_NOT_FOUND. The same narrowing applies to HEAD and to delete-by-name.
The GET /v1/storage/objects listing is unchanged.
What integrators should do
If you read or deleted an employee file with an app key and no employee session, pass that employee's
session in the Authorization: Bearer header, which makes their file reachable. The
GET /v1/storage/objects/{objectId} route this entry used to suggest does not exist in the API: a file
is read by key only. A public employee file also opens through the
GET /v1/public-storage/{portalId}/{objectId} link when anonymous access is enabled in the Bitrix24 account.
FIX-0903-7: employees of one app can save files under the same name
Before
A logical file name was one per app. If an employee of the account saved avatar.png, a second employee of the
same app got 409 STORAGE_KEY_OWNED_ELSEWHERE on a direct upload and 409 STORAGE_KEY_EXISTS on a
multipart one — even though the files would land at different addresses, because a per-employee file
address includes the employee identifier. The refusal never expired: the name stayed taken until the
first employee deleted the file.
After
The name is claimed per employee. The second employee gets 200 and their own object with its own
identifier; a repeated upload by the same employee still replaces that employee's own file. An
app-shared file and a per-employee file share one namespace, so a request that finds the name already
held by an object of the other ownership kind still gets 409 STORAGE_KEY_OWNED_ELSEWHERE.
FIX-0903-8: a multipart upload distinguishes who holds the logical name
Before
POST /v1/storage/objects/multipart/create answered 409 STORAGE_KEY_EXISTS for a taken logical name regardless of who held it — both when the object was yours and when it belonged to the other ownership kind: an app-shared file versus a per-employee file. The response could not tell those cases apart, although the actions that resolve them differ.
After
When the name is held by an object of the other ownership kind, a multipart upload answers 409 STORAGE_KEY_OWNED_ELSEWHERE — the same way a direct upload has long done. Every other taken-name case still answers 409 STORAGE_KEY_EXISTS, and the response status is unchanged in all cases.
What this means for integrators
No action is required: a refusal stays a refusal with the same 409 status, only the code in the body changes. If your client branches on the specific code of a multipart upload, add a branch for STORAGE_KEY_OWNED_ELSEWHERE — it means the name is held by an object of the other ownership kind and cannot be freed by switching employees.
BC-0903-9: smart process stage history by numeric entityTypeId, query parameters are now strict
Old format supported until: not provided
Before
GET /v1/stage-history accepted only four named types — deal, lead, invoice and
new-invoice. A smart process has no name, so its stage history could not be requested at all:
?entityType=128 answered 400 INVALID_ENTITY_TYPE without ever calling Bitrix24.
The sibling parameters (ownerId, typeId, categoryId, limit, offset, createdAfter,
createdBefore, stageId, stageSemanticId, statusId, statusSemanticId) meanwhile were
accepted in forms that answered SUCCESSFULLY:
?entityType=deal&ownerId[]=5 returned 200 and the history of owner 5, exactly as if the
value had arrived as a scalar. A repeat ?entityType=deal&ownerId=5&ownerId=6 returned 200,
silently taking the last value. A repeat with identical values
?entityType=deal&limit=50&limit=50 returned 200 and a correct result set.
After
entityType accepts a smart process numeric entityTypeId — ?entityType=128. The identifier
comes from GET /v1/smart-processes. Smart processes are stage-based, exactly like deals:
stageId, stageSemanticId and categoryId apply, and the response carries the same fields.
A numeric identifier that already has a named key (1, 2, 31) is refused and steered to that
key. Contact (3), company (4), quote (7) and the legacy invoice (5) have no stage history
and are refused as well. An identifier absent from the account returns 400 INVALID_ENTITY_TYPE
rather than a Bitrix24 error.
Every parameter listed above is accepted exactly ONCE and only as a single string value. All three
forms from the Before block now answer 400 INVALID_PARAMS — the bracket ones (?ownerId[]=5,
?ownerId[x]=1) and the plain repeat (?ownerId=5&ownerId=6) alike, including when the repeated
values are identical, and a MIXTURE of spellings (?ownerId[]=6&ownerId=5) — that last one answered
200 keeping only 5 of what was asked for. The bracket form ?ownerId[x]=1 used to answer 500.
The numeric parameters (ownerId, typeId, categoryId, limit, offset) are additionally
accepted only as an integer written in full: no sign, exponent, leading zeros or suffix.
?ownerId=1e3 returned 200 and the history of owner 1 instead of 1000, ?typeId=2abc the
history of type 2, and ?ownerId=abc dropped the owner filter altogether and answered WIDER than
asked. All three now answer 400 INVALID_PARAMS.
The entityType forms are tightened too: entityType[]= and entityType[x]= returned 500
instead of 400; a repeated entityType=a&entityType=b silently took the last value and is now
refused; entityType=constructor and entityType=__proto__ answered 200 with an empty result
instead of a refusal. The INVALID_ENTITY_TYPE message now names the numeric form too, not only the
four names.
The same class is closed in POST /v1/duplicates/find and POST /v1/triggers/fire: a body with a
non-string type or entityType — number, object or array alike — returned 500 instead of the
documented 400. In triggers/fire the same now applies to entityId and triggerId: an object in those fields
either crashed the request into a 500 or reached Bitrix24 as a meaningless value and answered 200
without firing anything. A MULTI-value array in entityId reached Bitrix24 glued into one string,
addressed no record and fired nothing — that is a 400 now. A ONE-value array worked ([5] was read
as 5 and the trigger fired) and keeps working. Numbers are still accepted in both fields and reach
Bitrix24 unchanged — a numeric triggerId that worked keeps working. A non-string entityType in duplicate search is now refused rather than ignored.
Both routes also answer 400 to a request sent with no body and no Content-Type header, where
they used to answer 500.
What integrators should do
Send every query-string parameter once and as a scalar value: ?ownerId=5 rather than
?ownerId[]=5 and rather than the repeat ?ownerId=5&ownerId=6. If the filter is assembled in a
loop, check that a key cannot be appended to the query string twice — a stray repeat used to pass
unnoticed and now answers 400 INVALID_PARAMS naming the parameter in the error text.
The old form is not kept accepted for a transition period: there is no support window and the refusal applies as soon as the update ships, so a client-side change ships together with the update rather than after it.
Clients that already send parameters as single scalars need no change.
FIX-0903-10: one request no longer pauses a Bitrix24 method with its own sub-calls
Before
When one request to the Vibecode batch and looping API methods repeatedly timed out on the same Bitrix24 method, it could trigger a 15-minute pause for that method by itself. Remaining sub-calls returned TIMEOUT_QUARANTINE, and the pause affected other requests from the same Bitrix24 account.
After
All sub-calls of one request now count as one attempt for each account-method pair. Separate requests can still trigger the protective pause after the configured number of consecutive timeouts. Successful and error response formats are unchanged.
Impact on integrators
No client changes are required. The fix covers POST /v1/batch, POST /v1/{entity}/batch, GET /v1/tasks/:taskId/comments, POST /v1/tasks/:taskId/comments, POST /v1/tasks/:taskId/comments/batch, GET /v1/task-time, and GET /v1/timeline-logs.
BC-0903-11: an employee photo that cannot become a file is refused on update
Old format supported until: not provided
Before
On employee update, a personalPhoto value Bitrix24 cannot read as file content reached Bitrix24 as a command to remove the current photo. The literal false behaved that way, and so did the booleans true and false, the number 0, a string with fewer than two base64-alphabet characters — !!! or ===, for example — an inline [file name, base64] pair whose content is one of those, and any other structure whose second value is missing, null or unreadable, the nested {fileData: [...]} and an explicit null or a structure in the content element included. The call answered with success after the deletion had happened, while the earlier refusal covered only an empty or whitespace-only string.
After
Such values are refused with INVALID_PARAMS before the Bitrix24 call on all three update surfaces, exactly like the empty string. A value with two or more base64 characters still travels to Bitrix24: it does become a file, and Bitrix24 itself decides whether that file is usable. On CREATE in POST /v1/users the new refusal does not apply.
What integrators should do
Do not put placeholders such as false, 0, true or empty strings into personalPhoto to mean "change nothing" — omit the field instead. To remove a photo, call DELETE /v1/users/:id/personal-photo.
NEW-0903-12: a command that removes an employee photo
The employee photo now has a command of its own for removal — DELETE /v1/users/:id/personal-photo. It removes the profile photo and answers 200 with the fields id, personalPhoto: null and removed: true. The call is idempotent: for an employee with no photo it succeeds as well. The operation is irreversible, Bitrix24 deletes the file itself, so uploading the same image again yields a new URL. The user scope is required, a read-only key does not run the command, and the rights decision belongs to Bitrix24 — on a refusal it answers 403 UPDATE_FAILED and the photo stays in place.
Writing the personalPhoto field removes the photo on no update surface, and null is not a way to do it either. On PATCH /v1/users/:id and in the per-entity batch, null is accepted and ignored, while the global POST /v1/batch refuses it with INVALID_PARAMS, because the sub-call encoder would turn it into an empty query-string value — the same remove-the-photo command. That difference between surfaces is now documented deliberately, and the behaviour of the calls did not change. To replace a photo you do not need a separate removal: send the [file name, base64] pair in personalPhoto to POST /v1/users or PATCH /v1/users/:id.
BC-0903-13: business process activity and robot creation returns a string code
Old format supported until: not provided
Before
POST /v1/bizproc-activities and POST /v1/bizproc-robots returned HTTP 201 with boolean data.id: true. This value could not be used as the code in PATCH or DELETE.
After
The same requests still return HTTP 201. For the regular Bitrix24 response true, data.id contains the string code submitted at creation; when Bitrix24 explicitly returns CODE, that value wins. The identifier can be used for subsequent PATCH and DELETE requests.
What integrators should do
Change the data.id type for these two responses from boolean to string and use the returned value as the code for updates or deletion.
BC-0903-14: re-uploading with a personal key now refuses where the object state requires it
Old format supported until: not provided
Before
Uploading again onto an occupied key with a personal developer key or in a server-owned context
always returned HTTP 200 and created a second row, so object-state checks were never reached.
After
The repeat now goes through the same state ladder as an app-bound key, and three situations return a
refusal. A different visibility on the repeat returns 400 STORAGE_VISIBILITY_MISMATCH: omit the
field or pass the stored value, which is named in the message. A multipart upload (Path C) onto an
occupied address returns 409 STORAGE_KEY_EXISTS before the session is created. A body with no
Content-Length onto an occupied address returns 409 STORAGE_REPLACE_REQUIRES_LENGTH.
409 STORAGE_UPLOAD_PENDING and 409 STORAGE_MULTIPART_IN_PROGRESS also become reachable — for
these keys they never fired before.
FIX-0903-15: re-uploading with a personal key replaces the file instead of creating a second object
Before
A personal developer key and a server-owned context created a SECOND object at the same physical
address when the same key was uploaded again. The first object's bytes were overwritten by the
second, its size was billed twice, and a read or delete by logical name could hit either row. The
dangerous-content-type gate for PUBLIC objects was evaluated against the declared visibility, so
text/html slipped in under a live public object.
After
Uploading again replaces the content of the existing object: object.id, createdAt, key,
physical address and visibility are preserved, while sizeBytes, sha256, contentType and
contentUpdatedAt are updated. The response remains HTTP 200. An eligible soft-deleted object is
revived exactly as it is for an app-bound key. The dangerous-content-type gate is evaluated against
the visibility of the row that was found, before any byte is written, so text/html can no longer
land under a live PUBLIC object.
FIX-0903-16: Node.js 20 deploy no longer depends on the package repository on a prepared image
Before
The Node.js 20 preparation step always contacted the external system package repository, even when every base package was already installed. An unavailable repository left the step without updates for a long time and then failed it.
After
A prepared image with the base packages does not contact the repository. When a package is still required, the network wait is bounded; the streaming response periodically reports elapsed time during lengthy preparation. The terminal response shape and successful status are unchanged.
BC-0903-17: currency sorting rejects a field Bitrix24 cannot sort by
Old format supported until: not provided
Before
GET /v1/currencies and POST /v1/currencies/search accepted any field name in sort and order. The Bitrix24 method crm.currency.list silently replaces an unknown key with its default order, so the answer came back 200 with a list in an order the client had not asked for and with no sign of an error. The same applied to the sort and order keys of an entity=currencies sub-call in POST /v1/batch.
After
Three fields are accepted: sort, id and fullName. Any other name — the non-existent bogus as well as the declared amount — returns 400 UNKNOWN_SORT_FIELD before Bitrix24 is called, and the message lists the accepted names. The documented ?order[sort]=asc is unchanged.
What integrators should do
Three classes of change, each needing its own check.
First: a request with an unknown field name now gets 400 instead of 200. Code that relied on such a request "just working" will now see an error — that is the fix, but it has to be handled.
Second: the fields amount, amountCnt, base, formatString, decimals, decPoint, thousandsSep, lid, dateUpdate and lang also answer 400. They never sorted anything: the platform method replaced the order with its default, so the successful answer was untrue.
Third, and quietest: ?sort=id and ?sort=fullName now really change the row order while staying 200. Both used to fall back to the default order silently. A paged walk over those fields via offset will return different pages than before the update, and there is no sign of it in the response status — re-check such walks.
Affected endpoints: GET /v1/currencies, POST /v1/currencies/search and the entity=currencies sub-call in POST /v1/batch — both the sort and the order key. For other entities the batch order key still reaches Bitrix24 as-is.
BC-0903-18: include accepts only resolvable relations
Old format supported until: not provided
Before
The site relation on GET /v1/pages/:id, requisite on GET /v1/companies/:id and GET /v1/contacts/:id, and quote on GET /v1/deals/:id were advertised as available. A request with such an include returned HTTP 200 but did not add the relation to _included. The same happened to deal, contact, and company on GET /v1/quotes/:id, contact and company on GET /v1/invoices/:id, and section on GET /v1/products/:id.
After
Quote, invoice, and product relations are returned in _included as an object or null when the foreign key is empty. site on pages, requisite on companies and contacts, and quote on deals are no longer advertised. Requesting these names returns 400 INVALID_INCLUDE.
What integrators should do
For a page, read siteId and request GET /v1/sites/:id. For a company, use GET /v1/requisites with entityTypeId=4 and entityId=<companyId> filters. For a contact, use it with entityTypeId=3 and entityId=<contactId>. For a deal, read quoteId and request GET /v1/quotes/:id.
BC-0903-19: input array in POST /v1/embeddings capped at 64 strings
Old format supported until: not provided
Before
POST /v1/embeddings accepted an input array of any length — there was no limit on the number of strings in one request.
After
The input array accepts at most 64 strings. A longer array is rejected with 400 invalid_request before anything is charged and before the model is called. Split a larger request into parts of 64 strings.
NEW-0903-20: speech-to-text error texts no longer name the recognition engine
The English texts of the ai_provider_timeout and ai_provider_unavailable errors returned by POST /v1/audio/transcriptions no longer name the recognition engine; codes, statuses and the transcription contract on the international platform are unchanged.
FIX-0903-21: a Vibe+ demo now opens access on .com
Before
An account holding a Vibe+ demo kept getting INT_VIBE_PLUS_REQUIRED: granting
the demo does not change the account's plan code, and the access verdict read
only that code. The capabilities.servers.create slot in /v1/me stayed
available: false, so retrying the action hit the same refusal.
After
A live demo opens access in trial mode, under the same limits as a Marketplace demo. A purchased Vibe+ plan on top of a live demo still grants full access, and an expired demo grants none.
NEW-0903-22: two new promo code refusal codes: Bitrix24 plan restriction
A promo code campaign can now narrow its audience by Bitrix24 plan. The restriction is a list of allowed plans; an empty list means there is no restriction — every campaign and promo code issued earlier behaves exactly as before, and a request that knows nothing about the new codes loses nothing.
When the list is set, both public promo code methods report the new codes: POST /v1/cowork/coupon/preview returns them in the reason field when valid=false, and POST /v1/cowork/coupon/redeem answers 409. There are two codes, and they must not be conflated.
COUPON_TARIFF_NOT_ELIGIBLE — the account plan has been read and is not on the list. The refusal is final: the code will not start working on this account by itself, so retrying the request is pointless.
COUPON_TARIFF_UNKNOWN — the restriction applies, but the platform has not established the account plan yet (this happens on a freshly connected account). The promo code is intact, the attempt does not consume the redemption attempt limit, and retrying later is the right action.
In both cases the promo code stays unredeemed, and the existing refusal codes are unchanged.
Important: for preview the response status is the same as before — 200 with valid=false — but the reason field now also carries these two values. A client that matches reason against a known set must add branches for them: COUPON_TARIFF_UNKNOWN falling into a default "the promo code is invalid" branch shows a final refusal where the right action is to retry later. The attempt-limit behaviour of a check is the same as that of a redemption: COUPON_TARIFF_UNKNOWN does not spend it, COUPON_TARIFF_NOT_ELIGIBLE does.