# API changes: August 7, 2026

[← Changelog](/docs/changelog) · [August 2026](/docs/changelog/2026-08)

### 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](/docs/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](/docs/source-storage) could not be deployed on that same server: [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/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](/docs/entities/activities/get) and
[update](/docs/entities/activities/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](/docs/entities/deals/create) 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](/docs/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](/docs/feedback/comments) answered `409 FEEDBACK_CLOSED`. The only workaround was changing the status through [PATCH /v1/feedback/:id](/docs/feedback/update).

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.

1. **The `409 FEEDBACK_CLOSED` handler.** A resolved ticket now answers `201` and 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: only `ARCHIVED` and `WITHDRAWN` stay closed.
2. **Branching on `authorType`.** For a second key of the same owner the value changed from `PLATFORM` to `USER`. Code that renders `PLATFORM` as "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.
3. **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 with `authorType: PLATFORM`.
4. **Closing your own ticket with a comment.** If you closed your own ticket through `POST /comments` with a `status` field, that route no longer works: for the author `status` is ignored silently, the response is `201`, and the status stays as it was. To withdraw a ticket, use [PATCH /v1/feedback/:id](/docs/feedback/update) with `status: WITHDRAWN`.
5. **Handling `429` on comments.** The operation gained a rate limit it never had. If your code posts comments in a batch or a loop, add handling for `429 RATE_LIMITED` and back off by the `Retry-After` header. 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](/docs/feedback/comments), [GET /v1/feedback/:id](/docs/feedback/get), [GET /v1/feedback](/docs/feedback/list)

### 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](/docs/infra/deploy/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](/docs/infra/deploy/deploy) and [POST /v1/infra/servers/:id/upload](/docs/infra/deploy/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](/docs/entities/requisite-presets/preset-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](/docs/entities/tasks/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](/docs/entities/smart-processes/fields) returned `daysBeforeClose` with a sentence-long label instead of a short name. [GET /v1/leads/fields](/docs/entities/leads/fields) returned `fm` labelled "FM". [GET /v1/timelines/fields](/docs/entities/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](/docs/feedback/list) 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](/docs/entities/orders/update) 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](/docs/batch) with `action: "update"`, and [POST /v1/batch](/docs/batch) with `action: "update"`. Creation is unchanged: [POST /v1/orders](/docs/entities/orders/create) and both batch creations still accept these fields and pass their values through. In the [GET /v1/orders/fields](/docs/entities/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](/docs/entities/basket-items/create).

### 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}](/docs/infra/servers/update) 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](/docs/infra/deploy/deploy), [PATCH /v1/infra/servers/{id}](/docs/infra/servers/update)

### 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](/docs/entities/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`.
