# API changes: September 4, 2026

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

### NEW-0904-2: the Supports BitrixMobile flag at application registration

`POST /v1/apps` accepts an optional `mobile: boolean` field, defaulting to `false`. When `true`, the platform reports the Supports BitrixMobile flag to Bitrix24 at application registration on the account, and the application becomes visible in the mobile client. The `mobile` field now appears on the application object too — in the creation response, [application data](/docs/apps/get), the [list](/docs/apps/list) and [relink](/docs/apps/relink-oauth). Applications created earlier carry `false`. The flag is set only at creation — `PATCH /v1/apps/:id` does not accept `mobile`. When the account registers the application through a path that cannot carry the flag, the application is created with `mobile: false` and the creation response carries a `warnings` line starting with `mobile:`. Existing requests without `mobile` keep working. Details — [Create an application](/docs/apps/create).

### BC-0904-3: the self-hosted refusal code is renamed to SELFHOSTED_NOT_AVAILABLE

> Old format supported until: not provided

**Before**

A self-hosted Bitrix24 account on the international installation was refused with
the code `INT_BOX_PARTNER_REQUIRED`. The message explained that access comes with
a partner licence, that the key carried no confirmed partner mark, and asked the
customer to re-register the module in the account. The button pointed at the
connect documentation: `details.upgradeUrl` and `alternatives[0].url` carried the
address of `/docs/connect-self-hosted-bitrix24`.

**After**

The same refusal arrives with the code `SELFHOSTED_NOT_AVAILABLE`. A client that
branches on the code must replace the string — the old code is no longer returned
on any surface.

The rest of the response is unchanged: the status is still HTTP 402,
`details.requiredTariffs` is still empty (no plan purchase clears this refusal),
and `alternatives` keeps the same members in the same order.

The message and the button address changed. The message no longer names the
reason and no longer asks for anything to be done in the account: self-hosted is
not available yet, access is being opened gradually, and the account's servers and
data are kept as they are. `details.upgradeUrl` and `alternatives[0].url` now
carry a `mailto:` support address — the only address where this is resolved.

### FIX-0904-4: trial deployment hints account for galaxy host eligibility

**Before**

[GET /v1/me](/docs/keys-auth/me) promised one-shot deployment whenever any running or sleeping galaxy host existed. If that host could not accept the app, [POST /v1/infra/servers](/docs/infra/servers/create) rejected the request and its hint suggested two-step creation, which then hit the trial limit of the account.

**After**

[GET /v1/me](/docs/keys-auth/me) shows the one-shot path only when a known host appears eligible and explicitly marks the prediction as advisory. [POST /v1/infra/servers](/docs/infra/servers/create) remains authoritative; when an ineligible host occupies the trial limit and trial enforcement is active, the response no longer recommends an unavailable two-step create.

**Impact on integrators**

Check `deployment.galaxyApp` before a one-shot create and follow `error.hint` after a refusal. Before using the two-step path, also check `capabilities.servers.create.available` and choose a plan from `capabilities.servers.create.limits.allowedPlans` in the same response.

### FIX-0904-5: a catalog card is no longer published without its Bitrix24 account binding

**Before**

Publication could complete with `b24CatalogSync.status` set to `SYNCED` even though the catalog card
had no binding to the sender. In that state, the new-version notification did not arrive in the
application chat and no reason was displayed.

**After**

When calling [POST /v1/infra/servers/:id/b24-catalog/publish](/docs/infra/servers/b24-catalog-publish),
such a card is no longer created. For a paired account, publication completes only after the card's
binding to the sender has been confirmed. Until it is confirmed, `b24CatalogSync.status` stays other
than `SYNCED`, `pendingOp` stays `ADD`, and `lastError` names the reason. The response is still HTTP
200, and a successful publication returns exactly what it returned before.

**Impact on integrators**

Requests do not need to change. If an integration monitors the asynchronous status,
`pendingOp=ADD` together with a status other than `SYNCED` means that the card has not been
published yet. Wait for the status to change or show the `lastError` value to the user.

### FIX-0904-6: expiresAt of presigned URLs now equals their signature lifetime

**Before**

[POST /v1/storage/objects/multipart/create](/docs/storage/upload/multipart-create) computed `parts[].expiresAt` from the requested lifetime — 24 hours for every part — and [GET /v1/apps/{id}/sources/{versionId}/download](/docs/source-storage/versions) and [GET /v1/infra/servers/{id}/sources/{versionId}/download](/docs/source-storage/servers) from a fixed 30 minutes; the redirect link of [GET /v1/storage/objects/{key}](/docs/storage/objects/get) was documented as valid for 10 minutes. The currently disabled [POST /v1/storage/objects](/docs/storage/upload/presigned) answers `503 STORAGE_PRESIGNED_UPLOAD_DISABLED` and computed `expiresAt` the same way. The URL signature could expire earlier, and storage answered `403` while `expiresAt` was still in the future.

**After**

`expiresAt` in these responses is taken from the URL signature itself and equals its lifetime. It may be shorter than the 24-hour multipart session and shorter than 30 minutes for download URLs; the redirect link may also expire before 10 minutes. Nothing changes for the disabled `POST /v1/storage/objects`: it still answers `503`, and once enabled its `expiresAt` will also come from the signature. The response remains HTTP 200, the field format is unchanged.

**Impact on integrators**

Schedule uploads and downloads by the `expiresAt` from the response and start as early as you can, not by the `uploadId` session lifetime and not by the documented 30 minutes. Clients that already relied on `expiresAt` change nothing.

### FIX-0904-7: the latin locales call Bitrix24 trial access a trial

**Before**

The latin locales called Bitrix24 trial access a demo, while Bitrix24 itself names it a trial on the international installation. The mismatch reached the contract too: [GET /v1/me](/docs/keys-auth/me) returned `Demo period` in `data.tariff.name`, and the `INT_TARIFF_REQUIRED` refusal told the reader to activate a paid plan "or its demo". Within a single activation flow neighbouring messages disagreed with each other: one said trial, the next said demo about the very same access.

**After**

The plan value and the refusal text in the latin locales are settled on the word trial. `GET /v1/me` returns `Trial period` for that plan, and the `INT_TARIFF_REQUIRED` refusal now offers a paid plan "or its trial". A client comparing this field by string should re-check the comparison: the codes, statuses and response fields themselves are unchanged, and a successful response stays successful. The Russian locale keeps its own wording.

This entry covers the contract: the plan value and the refusal text. At publication time the `apps.bindPlacements` capability note of the same endpoint and the documentation articles still said demo, and they were translated later, in FIX-0907-11.

### FIX-0904-8: batch currencies sub-call no longer returns meta.total 0 next to a non-empty data

**Before**

[POST /v1/batch](/docs/batch) with a `currencies` list sub-call published `data.meta.<id>.total: 0` and `hasMore: false` while `data.results.<id>` held records. The Bitrix24 method `crm.currency.list` reports envelope `total: 0` next to a non-empty result, and the batch door accepted that literal zero as the collection size. A client paging by `hasMore` under a `limit` smaller than the catalog read the first window and treated the set as exhausted, losing the tail.

**After**

On a counted sub-call the envelope zero no longer beats the measured set size: `data.meta.<id>.total` and `data.totals.<id>` report the size before the client-side window, and `hasMore` is computed as `offset + returned < total`, staying `true` while a tail remains. This matches the single endpoints, which already normalized this method behaviour. An uncounted sub-call (`params.withTotal: false`, a negative `params.start`) still gets no `total`, and a genuinely empty catalog still returns `total: 0`. The response remains HTTP 200.

### NEW-0904-9: a partner application can revoke its own key

A new [POST /v1/connect/revoke](/docs/partner-connect) endpoint follows RFC 7009: the application sends `client_id`, `client_secret` (public clients send none) and `token`, and exactly the presented key is killed. Its slot in the user's key limit for that Bitrix24 account is freed immediately, and requests with the key start answering `401 KEY_INACTIVE`.

A `200` also comes back when there was nothing to revoke — for an unknown token, for a key issued to another application and for an already revoked one — so the call is idempotent and cannot be used to probe whether someone else's keys are alive. A missing `client_id` or `token` gives `400 invalid_request`; an unknown client or a wrong secret gives `401 invalid_client`. A key issued before the client was deactivated can be revoked as well.

The endpoint is announced in the discovery document `/.well-known/oauth-authorization-server` through `revocation_endpoint` and `revocation_endpoint_auth_methods_supported`. Until now a key issued to an application could only be revoked by the user in the "Connected apps" section, by the application owner deleting the client outright, or by a platform administrator; all three keep working as before.

### BC-0904-10: offset in the statuses and deal-categories references works at any depth

> Old format supported until: not provided

**Before**

[GET /v1/statuses](/docs/entities/statuses/list) returned the same records for `offset=0`, `50`,
`100` and `300`. The Bitrix24 method behind this reference answers with the whole collection in
one response and applies no navigation, while the wrapper cut that response from the beginning —
the client received the first page in place of the fiftieth. At an offset that was not a multiple
of 50 the window repeated with a period of 50: `offset=130` returned the same records as
`offset=30`.

An offset walk did terminate, but it collected duplicates and never reached the end of the
reference: out of 267 records only about 50 were reachable through the list, while `meta.total`
reported an honest 267. There was no error at any step — every response came back with status 200.
[GET /v1/deal-categories](/docs/entities/deal-categories/list) and list sub-calls inside
[POST /v1/batch](/docs/batch) behaved the same way.

A list sub-call inside a batch with NO explicit `limit` also behaved differently from the same
list issued as a single request: a single [GET /v1/statuses](/docs/entities/statuses/list)
returned 50 records by default, while the batch sub-call returned the entire reference — all 267
records in one response.

**After**

The `[offset, offset + limit)` window is computed on the Vibecode side over the full set, in the
order the core returned. `offset=50&limit=5` yields records 51 through 55, a `limit` above 50 no
longer truncates the tail, `meta.total` equals the size of the filtered set, and `meta.hasMore`
turns `false` on the last page. An offset walk terminates and covers every record exactly once.
The response remains `200`.

A list sub-call inside a batch now follows the same default as a single request: with no explicit
`limit` it returns the first 50 records and `hasMore: true`, not the whole reference.

Sorting and filtering are still performed by Bitrix24, so the `sort` parameter behaves as before.
The same behaviour applies to [POST /v1/statuses/search](/docs/entities/statuses/search) and to
list sub-calls inside [POST /v1/batch](/docs/batch).

**Impact on integrators**

Loops that previously re-read the same records and never reached the end of the reference now
return the full selection — they need no code change.

One case does require a code change: a [POST /v1/batch](/docs/batch) sub-call listing statuses or
deal categories with no explicit `limit`. It used to return the whole reference; it now returns
the first 50 records. There is no error — the response comes back with status `200` and
`hasMore: true` — so the remaining records are lost silently unless they are requested.

What to do: either set `limit` explicitly, or read the reference page by page, advancing `offset`
by the page size until `meta.hasMore` becomes `false`. The second option is preferable — it does
not depend on the size of the reference and works on any Bitrix24 account.

### BC-0904-11: Workday history now returns the time-zone offset

> Old format supported until: not provided

**Before**

[GET /v1/workday/records](/docs/workday/records) required only `timeman`. Records did not contain a required `tzOffset`, so a client could not reliably obtain the employee's local time.

**After**

The endpoint requires `timeman` and one of `user_brief`, `user_basic`, or `user`. Every record contains a required `tzOffset`: seconds east of UTC calculated for the `startTime` instant using the historical rules of the current IANA `TIME_ZONE` identity in the employee profile. The value is not proof that this zone was assigned when the record was created. The `startTime` and `endTime` strings are unchanged. A non-empty page returns `502 BITRIX_UNAVAILABLE` if the current profile zone or its offset cannot be determined reliably; the API cannot detect a zone reassignment after record creation and does not promise a 502 for it.

**What integrators should do**

Reissue existing keys with `timeman` and one user-family scope, then process the required `tzOffset` in the [GET /v1/workday/records](/docs/workday/records) response.

### BC-0904-13: Entity read calls now have a per-account rate limit

> Old format supported until: not provided

**Before**

Entity reads — list (`GET /v1/deals` and the same call on every entity), search (`POST /v1/deals/search`), aggregate (`POST /v1/deals/aggregate`, including the legacy `GET …/aggregate`), field definitions (`GET /v1/deals/fields`), related records (`GET /v1/deals/{id}/contacts`, `…/activities`), product rows (`GET /v1/deals/{id}/products`) — and the per-entity batch (`POST /v1/deals/batch` and the same on every entity) accepted requests without a rate limit — while the global `POST /v1/batch` already carried one. One client reading a list more than roughly ten times per second slowed lists and search for every account on the same instance, up to request timeouts. A `HEAD` request to a list was served as a full `GET`: the data was read in full and only the headers were returned.

**After**

Every such read is capped at **300 requests per minute per Bitrix24 account**; all API keys of one account share one limit, and each entity and each operation is counted separately. On exceeding it the Vibecode API answers `429 RATE_LIMITED` with a `Retry-After` header; the body carries no delay — read it from the header. The current limit value arrives in the `x-ratelimit-limit` header. The per-entity batch is capped tighter — **30 requests per minute per Bitrix24 account**, the same as the global `POST /v1/batch`: one such request fans out into hundreds of Bitrix24 calls. The `HEAD` method on these paths is no longer served by the read handler — use `GET` with `limit=1` instead. Successful responses, error codes and payload formats are unchanged.

**What integrators should do**

Handle `429` on every entity read and on the batch the same way as on `/v1/search` and `/v1/batch`: wait for the delay in `Retry-After` and retry. If the limit triggers regularly, read less often and in larger pages (`limit` up to 5000 per call), cache results on your side and do not run identical reads in parallel. If you probed list availability with `HEAD`, use `GET` with `limit=1` instead. Make batches larger rather than more frequent: one request takes up to 500 items.

### NEW-0904-14: invoices support include=deal

[GET /v1/invoices/:id](/docs/entities/invoices/get), [GET /v1/invoices](/docs/entities/invoices/list), and [POST /v1/invoices/search](/docs/entities/invoices/search) now accept `include=deal`. The related deal from `parentId2` is returned in `_included.deal`. When no relation exists, the value is `null`.
