For AI agents: markdown of this page — /docs-content-en/changelog/2026-08-07.md documentation index — /llms.txt
API changes: August 7, 2026
NEW-0807-1: CRM record import: the original author and dates, with no automation run
An existing database can now be moved into the CRM with one request per entity: POST /v1/leads/import and the same route on deals, contacts, companies, quotes, invoices and smart-process items. Up to a hundred records at a time, an items array in the body, and the same record fields the ordinary create takes.
Import differs from create in three ways, and all three are properties of the Bitrix24 operation itself rather than parameters of ours. It checks a separate "import" permission that the account administrator grants explicitly. It does not run robots, triggers or business processes configured to fire on record creation. And it accepts the service fields the ordinary create silently ignores: who created the record (createdBy), who changed it (updatedBy), who moved it to its stage (movedBy) and the matching dates. Only an account administrator may set them — for any other user Bitrix24 refuses that record, while the rest of the batch is still created. The available set differs per object; the exact list comes from GET /v1/leads/fields, where those fields are marked importable.
The creation date has a window set by Bitrix24: no later than the current moment, and no earlier than that of the newest existing record of this object. So a full history transfer works into an empty CRM, or into one whose records are all older than what you are moving; Bitrix24 will not let you backdate records into a populated CRM. If you do not need the history, omit the dates — the author is set without them.
The response comes back with status 200 even when some records failed: import is not transactional, so the outcome is read per record in results[], with the totals in summary. Always check summary.failed — the status says "the request was processed", not "everything was created". A repeated import creates duplicates; if repeats are possible, write your previous system's identifier into originatorId and originId.
The route is rate-limited per account so a bulk transfer cannot block the account's other integrations. Import in a single stream: order is guaranteed within one request but not between parallel ones, and Bitrix24 requires non-decreasing creation dates.
Details, error codes and examples are on the CRM record import page.
FIX-0807-2: a source version deploys by its number on its own server
Before
A version saved through POST /v1/infra/servers/:id/sources could not be deployed on that same server: POST /v1/infra/servers/:id/deploy with body { "source": { "versionId": "v1" } } answered 400 SOURCE_VERSION_REQUIRES_APP whenever the server's owning key was not bound to an application — that is, on a personal vibe_api_* key. The only way around it was manual: download the archive by link and deploy it as { "source": { "url": … } }.
After
When the server belongs to the same key that makes the call, the version is looked up in that server's context, so deploying by versionId works, personal keys included. Not found on the server and the owning key is bound to an application — the lookup falls back to the application's context, as before. Found nowhere — 404 SOURCE_VERSION_NOT_FOUND; the message now names the server and the save endpoint instead of an application.
The SOURCE_VERSION_REQUIRES_APP code is not removed but narrowed: it now arrives only where the server belongs to a key other than the calling one — a management key, or access through an application card. If you branch on this code, keep that branch.
Integrator impact
Nothing to change. A call that used to be rejected now goes through, and the sha256 field in the response is preserved.
One rare exception: when a server and its application both hold a version under the SAME number — which happens for a version saved before the server was rebound to another key — the server's version now wins over the application's. The sha256 field in the response tells you which one was deployed.
FIX-0807-3: reading and updating a nonexistent activity answers 404, not 422
Before
GET /v1/activities/{id} and PATCH /v1/activities/{id} with an id that does not
exist on the account answered 422 with code BITRIX_ERROR and a message like
Bitrix24 API error: 400. The cause is upstream: on these two methods Bitrix24
returns a refusal whose error code and description are both empty, so there was
nothing to recognise "no such record" by. Meanwhile DELETE /v1/activities/{id}
on the same id already answered 404, because there the account does send an
error message. One and the same missing id produced two different answers
depending on the verb, and both documentation pages —
get and
update — promised 404.
After
Both methods answer 404 with code ENTITY_NOT_FOUND and the message
Activity is not found. — the same one DELETE returns. The rule is bound to
these two methods and fires only when the account sent neither a code nor a text:
a refusal carrying any code or message (including a validation error on update)
is unchanged. The error list in the documentation did not change — the response
did, and now it matches.
FIX-0807-4: a body-less request reaches its handler instead of failing at parse time
Some HTTP clients (axios, PowerShell Invoke-RestMethod, a few fetch wrappers) attach
Content-Type: application/x-www-form-urlencoded to every POST, PATCH and DELETE — even when
they send no body at all. Others send no Content-Type whatsoever. Neither form reached the
handler before this fix.
Before
POST /v1/deals with no body and no Content-Type header
answered 500 INTERNAL_ERROR — the handler failed on the empty body before it could report
that there was nothing to create. The same call with an empty body and an
application/x-www-form-urlencoded header answered 415 Unsupported Media Type before the
API key was even checked. POST /v1/chats/events/subscribe —
which needs no body at all — answered 415 with the form header and 400 with an empty body
under application/json.
After
An empty body is accepted whatever the header says: POST /v1/deals with no body answers
400 EMPTY_CREATE_BODY, the same reply POST /v1/deals with a {} body already gave, and
POST /v1/chats/events/subscribe with no body succeeds.
A Content-Type header on an empty body no longer gets in the way anywhere across entities
(/v1/deals, /v1/contacts, /v1/tasks and the other generated routes, batch and aggregate
included), chats (/v1/chats/*), custom fields (/v1/userfields/*,
/v1/items/:entityTypeId/userfields), the knowledge base (/v1/note/*) and keys
(/v1/portals, /v1/keys). The separate case of no Content-Type header at all is a
different failure, and it is closed on entities: a request with neither body nor header now
gets the ordinary field-validation reply instead of a 500.
Impact on integrators
Nothing to change: a request that worked keeps working. A non-empty body under an unknown
Content-Type is still rejected with 415 — the same status as before; 413 now arrives
only when the body really is over the size limit. A malformed JSON body under
application/json now answers 400 INVALID_JSON_BODY on all of the routes listed above
(chats previously returned Fastify's own parser code).
BC-0807-5: commenting on your own ticket no longer looks like a platform reply
Old format supported until: 07.08.2026
Before
The ticket author was the specific key that created it. A comment sent with another key of the same owner (or a ticket filed from the dashboard and followed up with a key) took the platform branch: it was recorded as authorType: PLATFORM, moved the ticket to AWAITING_USER, and overwrote Feedback.resolution with its own body. The key that created the ticket could not follow up on a resolved one at all — POST /v1/feedback/:id/comments answered 409 FEEDBACK_CLOSED. The only workaround was changing the status through PATCH /v1/feedback/:id.
On top of that, Feedback.resolution acted as a mirror of the team's last reply: any comment with the vibe:feedback scope overwrote the resolution text, and there was no way to get the previous value back. The comment operation had no rate limit at all.
After
Authorship is the key owner. A ticket created by another of your personal keys, or filed from the dashboard, is yours: the comment is recorded as authorType: USER, the resolution field is not overwritten, and a supplied status is ignored. One condition: such a key needs the vibe:feedback scope — without the scope, only the key that created the ticket counts as yours. The rule does not extend to application keys and management keys — there the key owner and the person writing are different people.
An author comment on a RESOLVED ticket brings it back into the queue (NEEDS_REVIEW) and clears resolvedAt / resolvedBy; the resolution text is kept. ARCHIVED and WITHDRAWN stay closed and still answer 409 FEEDBACK_CLOSED.
The resolution field is filled only by a comment that closes the ticket (target status RESOLVED or ARCHIVED). With any other status the field is left alone, and the comment text still reaches the author by email and is visible in the thread.
The comment operation gained a rate limit — 20 comments per minute, matching the same operation in the dashboard. The counter is shared per KEY OWNER: several of your own keys share one budget. Going over returns 429 RATE_LIMITED with a Retry-After header.
What integrators should do
Five places need edits, and they are worth locating in your code before you update.
- The
409 FEEDBACK_CLOSEDhandler. A resolved ticket now answers201and returns to the queue. If that code was your "the ticket is closed, stop writing" signal, move the check to the status in the response: onlyARCHIVEDandWITHDRAWNstay closed. - Branching on
authorType. For a second key of the same owner the value changed fromPLATFORMtoUSER. Code that rendersPLATFORMas "a support reply" will now render it as a user message — which is correct, but any logic hanging off that branch needs a second look. - Reading
resolution. It no longer works as "the team's last reply": it holds the verdict of the last closure, and on a ticket that was never closed the field is empty. For the team's latest answer, read the comment thread — the last entry withauthorType: PLATFORM. - Closing your own ticket with a comment. If you closed your own ticket through
POST /commentswith astatusfield, that route no longer works: for the authorstatusis ignored silently, the response is201, and the status stays as it was. To withdraw a ticket, use PATCH /v1/feedback/:id withstatus: WITHDRAWN. - Handling
429on comments. The operation gained a rate limit it never had. If your code posts comments in a batch or a loop, add handling for429 RATE_LIMITEDand back off by theRetry-Afterheader. The budget is per key owner, so minting a second key does not widen it.
There is no parallel support for the previous behaviour: the old way of filling resolution was the very defect this change fixes — there is nothing to keep running.
Affected endpoints: POST /v1/feedback/:id/comments, GET /v1/feedback/:id, GET /v1/feedback
BC-0807-6: an AI agent's server no longer accepts an application deploy
Old format supported until: 07.09.2026
Before
POST /v1/infra/servers/{id}/deploy accepted an archive for a server owned by
an AI agent. The deploy went through, the agent's own code was overwritten by
the application, and the agent went silent. It still reported as running, and
neither the response nor the UI showed a trace. For the same reason an agent's
server could be reused for an application when creating a new server under the
same name, or when re-binding an application.
After
Such a deploy is refused with 403 and the code AGENT_SLOT_DEPLOY_FORBIDDEN;
the message names the working alternative — the Retry button on the agent card,
or a separate server for the application. The reuse paths no longer pick an
agent's server: a fresh one is created instead. Managed bots are unaffected —
deploying their own code through the same call is their supported path.
Minting a maintenance key for an agent whose server is gone now answers 409
with the code AGENT_SERVER_GONE instead of handing out a key with nowhere to
go. Reading the key state still answers 200, with a new reason field set to
SERVER_GONE.
NEW-0807-7: `GET /v1/cowork/state` reports a scheduled downgrade
Before
Moving to a lower tier applied immediately and wiped the paid month, so there was nothing to report: the tier in the response changed at that same moment.
After
A downgrade is now queued for the end of the paid period, and the subscription
object gained a pendingTier field — the tier code the seat will move to at the next
charge, or null. The date is the existing currentPeriodEnd field.
The field is additive: clients that do not read it keep working. A cancelled
subscription always returns null — a cancellation outranks a plan, and a seat that
is closing must not be promised a tier.
FIX-0807-8: the galaxy host now fetches the source archive itself
Previously a dedicated agent action delivered the archive to the galaxy host under a hard ninety-second ceiling. The host now downloads and unpacks it on its own, verifying size and checksum against the saved version in place. The ceiling is gone, and failures are named honestly: an expired link, a dropped connection, no free space and a corrupt archive no longer collapse into one "unpack failed" message.
Error codes and response fields are unchanged; UPLOAD_NO_SPACE is added for an out-of-disk host. An unrecognised archive format and links supplied in the request body keep the previous path. Affects POST /v1/infra/servers/:id/deploy.
Separately, extractTo is now validated on the platform side, not only on the machine. The set of accepted paths is unchanged on both POST /v1/infra/servers/:id/deploy and POST /v1/infra/servers/:id/upload, which had no check of its own — the same values are rejected as before, but immediately and with a clear INVALID_EXTRACT_TO code.
FIX-0807-9: a deploy no longer fails while stopping a slow application
Before
A repeat POST /v1/infra/servers/:id/deploy over a running application that does not exit
immediately on SIGTERM failed at the stop_existing step after ~45 seconds:
DEPLOY_TIMEOUT, Deploy step timed out: GATEWAY_TIMEOUT: no response within 45s. The new
version was never rolled out. The platform allowed the stop 15 seconds while the operating
system on the server allows up to 90, so an application needing 45–90 seconds to shut down
failed the deploy every time. The hint pointed at repairing the tunnel — a false lead.
After
The stop_existing step now waits as long as the stop is allowed to take on the server (the
step budget is 105 seconds) and never aborts the deploy: when the stop could not be
confirmed — no result arrived, or the server answered that it failed to stop — the step returns
warning stating honestly that the outcome is unknown, and the deploy continues.
The second case was previously invisible: the step reported ok, so a failed stop left no
trace anywhere. The neighbouring clean step had the same hole: it too could report ok having
deleted nothing when the server never ran the command, and the deploy then failed two steps
later with a message that named no cause. Such a case now stops the deploy at once and says
why. When the port is also still held, the warning keeps both facts. The response stays
success: true; the step status is visible in data.steps[].
FIX-0807-10: renaming a requisite preset field is checked before the write
Before
PATCH /v1/requisite-presets/:presetId/fields/:id carrying a fieldName that is not
among the available ones answered 200 and {"updated": true}, and the nonexistent
name really landed on the preset row. The Bitrix24 update method, unlike the add
method, does not validate the name and accepts any string — so the row kept a name
with no field behind it and stopped showing data.
After
When fieldName in the request body differs from the name the row already carries,
the name is checked against GET /v1/requisite-presets/:presetId/fields/available
before the write. A name outside that list returns 400 with code
INVALID_FIELD_NAME and the update is not performed; no field of the row changes. A
name another row of the same preset already holds is absent from the available list
and is rejected too. Renaming to an available name works as before, and the name's
letter case is normalised to the spelling Bitrix24 returned.
Impact on integrators
Three answers changed. A garbage name returns 400 instead of 200: that 200 used
to store a name with no field behind it, which also lost the other fields of the same
request — silently. A fieldName sent as something other than a string also returns
400 INVALID_FIELD_NAME: such a value used to reach Bitrix24 and settle into the row
as the word Array. A request carrying fieldName for a row that does not exist
answers 404 before the write instead of relaying the Bitrix24 answer.
A request without fieldName behaves as before. A request carrying the name the row
already has still succeeds, but costs one more Bitrix24 call: to learn that the name
is unchanged, the platform reads the row first.
NEW-0807-11: field schema for task time tracking
GET /v1/task-time/fields has been added — the machine-readable field set of a time entry. Each of the ten fields carries a type, a read-only flag, a label and a description. Until now the field set was only described in prose, and a client could not fetch it with a call.
The schema is the same for every task, so the path is flat. The nested GET /v1/tasks/:taskId/time/fields still returns 400 WRONG_PATH, but now names the correct path in the error text. The request needs the task scope and makes no Bitrix24 call.
The fields that look numeric — id, taskId, userId, seconds, minutes, source — are declared as strings, because strings are what the responses actually carry. The userId field is marked createOnly: it is accepted on creation and refused on update.
Affected endpoints: GET /v1/task-time, GET /v1/task-time/fields.
FIX-0807-12: labels and descriptions for the daysBeforeClose, fm and FILES fields in the /fields response
Before
Three fields that Bitrix24 returns live came back with no explanation, and two of them also carried an awkward label. GET /v1/smart-processes/fields returned daysBeforeClose with a sentence-long label instead of a short name. GET /v1/leads/fields returned fm labelled "FM". GET /v1/timelines/fields returned FILES with a label but no description, so the attachment format had to be looked up on the comment-creation page.
After
All three fields now carry a description. For daysBeforeClose the label is shortened to a short name and the former long text moved into the description. For fm the label is replaced with a human-readable one, and the description points at the flat phone and email fields, which expose the same data in a form that is easier to read and write. For FILES the label stays exactly as Bitrix24 sent it — it depends on the account language — and the description states the attachment format for both writing and reading.
Impact on integrators
Nothing to change: the field type and the read-only flag (readonly) still come from Bitrix24 and did not change. If your code shows the label of these fields to a user, the text will differ — it is read from the response rather than stored on your side.
FIX-0807-13: the feedback list now honours the bracket filter form
Before
GET /v1/feedback?filter[status]=RESOLVED answered 200 with the whole accessible list: the bracket form was parsed but never read, so both the records and total came back unfiltered. Same for filter[category]. Only the flat form worked — ?status=RESOLVED.
After
Both forms behave the same. GET /v1/feedback applies filter[status] and filter[category] with the same validation as the flat params: case-insensitive, and an unknown value returns 400 INVALID_FILTER_VALUE instead of silently returning everything. When both forms are sent, the flat one wins — requests that worked before keep their exact answer. A value that is not a single value (filter[status][]=NEW) is also rejected with 400 INVALID_FILTER_VALUE.
FIX-0807-14: updating an order no longer loses the amount, the mark, and its reason silently
Before
PATCH /v1/orders/:id accepted price, marked, and reasonMarked and answered 200, but Bitrix24 does not save these fields on update. For marked and reasonMarked the value simply disappeared. For price it was worse: the amount is recalculated from the basket items, so a request with a manual amount did not change it, and with an empty basket the stored amount became 0 — meaning an update sent with any other field zeroed the order's price. Nothing in the response said so.
After
All three fields are rejected on update with 400 READONLY_FIELD before Bitrix24 is called — on all three write surfaces: the single PATCH, POST /v1/orders/batch with action: "update", and POST /v1/batch with action: "update". Creation is unchanged: POST /v1/orders and both batch creations still accept these fields and pass their values through. In the GET /v1/orders/fields response such a field is flagged readonlyOnUpdate: true, to tell it apart from readonly (not allowed on creation either) and from createOnly (the value is immutable after creation — which is not true of an order amount, Bitrix24 recalculates it).
Impact on integrators
There is no parallel support window for the previous behaviour: the previous behaviour was that the value was silently lost. If your update request sent these fields, remove them from the body, otherwise it will start answering 400. The most common case is a client that reads the whole order and sends the object back: drop price, marked, and reasonMarked from such a body. To change the order amount, edit the basket items.
NEW-0807-15: deploy now reports a displayName or description it did not apply
Deploy seeds displayName and description, it does not rename them: displayName is written only while it still equals the server's technical identifier, description only while it is empty. A value that conflicted with an already-set field used to be dropped silently — the response was a plain 200 with no sign that the field had not been written.
Such a response now carries an extra warnings[] entry: it names the dropped fields, confirms the deploy itself succeeded, and warns that re-sending will not change them. It also includes a ready-to-paste body for PATCH /v1/infra/servers/{id} with the current name already filled in — that endpoint requires displayName, so the sample prevents accidentally overwriting the name while editing only the description. The entry is appended last, and the write behaviour is unchanged.
The rename operation is now declared in the machine-readable API description (GET /v1/openapi.json) too — the schema previously claimed no rename existed in this API.
Affected endpoints: POST /v1/infra/servers/{id}/deploy, PATCH /v1/infra/servers/{id}
FIX-0807-16: reopening a ticket no longer erases the resolution text
Before
PATCH /v1/feedback/:id carrying only a status — for example {"status":"REVIEWING"} — cleared the resolution field when it returned a ticket from RESOLVED, WITHDRAWN, or ARCHIVED, even though the field was absent from the request body. The response was 200 and said nothing about the loss. If the team's answer had not been duplicated in a comment, there was no way to recover it.
After
Returning to an active status clears resolvedAt and resolvedBy only. The resolution field is left unchanged when you do not pass it: a ticket returning from ARCHIVED keeps its archive reason too. To replace the text, pass resolution in the same request; to clear the field, pass "resolution": null. The comment path (POST /v1/feedback/:id/comments) already left resolution alone — both surfaces now agree.
BC-0807-17: an activities aggregate needs a narrowing filter
Old format supported until: 07.02.2027
Before
POST /v1/activities/aggregate accepted a request with no filter. On a small account it answered in a second; on a large one it never answered: the very first call to Bitrix24 (counting every activity in the account) did not fit into the time allowed for one call, and the client got 503 BITRIX_TIMEOUT with a Retry-After header and a hint saying reads are safe to repeat. Repeating produced the same result, because the cause was not transient. The documentation, meanwhile, offered {} as "the fastest query".
meta.truncated meant exactly one thing: "more than 5000 records matched the filter". If some pages of records never reached us, the response came back with truncated: false — that is, the numeric aggregations and the groups were computed over part of the records and the response did not say so.
After
An activities aggregate requires one narrowing out of three: the ownerTypeId + ownerId pair, or responsibleId, or a date bound on createdAt / updatedAt / deadline. The platform switches the requirement on per account. While it is off, behaviour is unchanged; once it is on, a request with no narrowing gets 400 MISSING_REQUIRED_FILTER — message lists the accepted narrowings and a ready-to-paste example body, and Bitrix24 is not called at all.
Independently of that switch, a request without a narrowing that Bitrix24 failed to answer in time now returns 422 AGGREGATION_LIMIT_EXCEEDED instead of 503: the refusal is terminal, there is no Retry-After header, and the text says what to do instead of repeating. A request with a narrowing still gets 503 plus Retry-After on a timeout — there, repeating is honest advice, because we do not know why it was slow.
Both answers also come from the deprecated GET /v1/activities/aggregate — the rule cannot be side-stepped by calling it.
meta.truncated now means "some records never reached us" in both the old case (a selection wider than 5000) and the new one (fewer records processed than matched the filter). In the second case meta.recordsShortfall arrives alongside it — how many records are missing. count and meta.totalRecords stay complete: only data.groups and the numeric aggregations are partial. Important: this half of the change applies to the aggregate of ANY entity, not only activities: previously such a response came back with truncated: false, i.e. the incompleteness was reported nowhere.
The list of accepted narrowings, and whether the requirement is on for the account right now, arrive in data.aggregateFilterRequirement of the GET /v1/activities/fields response: the anchors field carries the narrowings, the enforcement field is either enforced or advisory. The static narrowings, without the account state, are also in GET /v1/guide.
What integrators should do
Add one of the narrowings to any activities aggregate — that is enough both before and after the requirement is switched on. If your code has a 503 branch for this endpoint today, add a 422 branch and do not repeat the request from it. If your code reads meta.truncated, note that it now also rises when records are lost, and look at meta.recordsShortfall.
FIX-0807-18: a file in a CRM field no longer hits the 1 MB wall, and the refusal code is documented
Before
The value of a "File" custom field travels in the request body as base64, and the body was capped at 1 MB on every method. The practical ceiling for one file was around 750 KB: PATCH /v1/deals/{id} with anything larger answered 413 with the FST_ERR_CTP_BODY_TOO_LARGE code, which appears on no documentation page. The same ceiling hit bot and chat file uploads.
After
The body is capped at 40 MiB on record create and update (POST /v1/{entity}, PATCH /v1/{entity}/{id}, including POST /v1/items/{entityTypeId}), and on POST /v1/bots/{botId}/files and POST /v1/chats/{chatId}/files — just under 30 MiB of the original file after base64. Every other method keeps the previous 1 MB ceiling: search (POST /v1/{entity}/search), batch calls, service methods.
The 413 refusal code is now PAYLOAD_TOO_LARGE on every /v1/ method except the AI routes (/v1/ai/*, /v1/chat/*, /v1/audio/*, /v1/models), which keep their OpenAI-compatible error envelope. It is the same code the edge layer already returns at its own threshold. The change also covers the streaming source uploads for apps (POST /v1/apps/{id}/sources) and servers (POST /v1/infra/servers/{id}/sources), and the 413 on a wrong Content-Type for app publishing, placement binding and document template creation. The former internal code no longer appears in responses.
The order of checks changed in favour of security: on record writes and on bot and chat file uploads the key is verified before the body is read. A request with no key or a wrong key now gets 401 where it could previously get 400 about unparsed JSON or 413 about size.
A new 429 refusal appeared, with the LARGE_BODY_BACKEND_BUSY code and a Retry-After: 5 header: the number of bodies over 1 MB processed at the same time is bounded. It protects server memory — the raised ceiling is not self-limiting, and every call waiting in the queue holds its own body. An ordinary client will never see it; a multi-threaded bulk upload will, and the right reaction is the one for any 429: wait and retry.
Mind the clock: the call to Bitrix24 is capped at 15 seconds with no retry, so a file right at the edge may fail with BITRIX_TIMEOUT on a slow account. Leave headroom, or move large files to a "File (Drive)" field via POST /v1/files/upload.