# API changes: June 29, 2026

[← Changelog](/docs/changelog) · [June 2026](/docs/changelog/2026-06)

### FIX-0629-1: org structure node search now searches by name

**Before**

[POST /v1/humanresources/nodes/search](/docs/humanresources/nodes/search) proxied to `humanresources.node.list`: the node type was set inside `filter`, there was no name search at all, and a top-level `{ "type": ..., "name": ... }` body (or a request with no body) returned `400` or `500`.

**After**

The endpoint now wraps `humanresources.node.search`. Two fields are required at the top level of the body — `type` (`DEPARTMENT` or `TEAM`) and `name` (a substring of the name). Optional are `parentId` and `pagination.limit` (default 50, maximum 200). It returns nodes whose name contains `name`, in a flat `data` with `meta` (`total`, `hasMore`). The `filter`, `order` and `select` fields are no longer accepted.

**Integrator impact**

Send `{ "type": "TEAM", "name": "<substring>" }` at the top level instead of the former `{ "filter": { "type": "TEAM" } }`. To enumerate all nodes of a type without name search, use [GET /v1/humanresources/nodes](/docs/humanresources/nodes/list) with `?type=...`.

### NEW-0629-2: AI quota off-peak hours schedule

Added the `GET /v1/off-peak` endpoint — the off-peak (Time-of-Use) discount schedule for the AI quota. The response carries the price multiplier right now (`currentMultiplier`), the next window when it gets cheaper (`nextWindow`), a 24×7 grid by hour and weekday (`grid`), the current grid cell (`nowCell`), and the schedule timezone (`timezone`). The discount applies to quota-metered usage only — the quota drains slower during these hours; wallet pay-per-token charges are unaffected. The optional `model=<id>` parameter returns a specific model's schedule instead of the platform default. Requires the `vibe:ai` scope. While off-peak is not enabled, the response is `{ "enabled": false }`.

### BC-0629-3: vibe-search provider slug removed

> Old format supported until: 26.12.2026

**Before**

The `provider` field in [POST /v1/search](/docs/search/run) and [POST /v1/research](/docs/search/research) accepted the `vibe-search` slug — a separate platform engine added on 2026-06-06. It was also listed among the slugs in [GET /v1/search/providers](/docs/search/providers).

**After**

The `vibe-search` slug is removed. The platform search engine on every instance is `bitrix-search` — which upstream backs it is instance-dependent. A request with `provider: "vibe-search"` now returns `400 INVALID_REQUEST` (the value fails validation). research support for `bitrix-search` is likewise instance-dependent — see [GET /v1/search/providers](/docs/search/providers).

**What integrators should do**

If your request explicitly passed `provider: "vibe-search"`, replace it with `bitrix-search` or omit the `provider` field to use the instance default engine (shown by the `defaultProvider` field in [GET /v1/me](/docs/keys-auth)). The `vibe-search` slug was not the default engine on any production instance, so only integrations that hardcoded it are affected.

### FIX-0629-4: A null field value via POST /v1/batch no longer writes the string "null" into the field

**Before**

In a composite `POST /v1/batch`, a create or update with a field value of `null` (for example `{"entity":"deals","action":"update","entityId":123,"params":{"comments":null}}`) wrote the **literal string `"null"`** into the field.

**After**

The field receives an empty value, which Bitrix24 interprets by field type: text is cleared, numeric becomes `0`, a date is left unchanged. The literal string `"null"` is no longer written and no error is raised. This matches the behavior of a single `PATCH /v1/{entity}/:id` with `null`. The per-entity `/v1/{entity}/batch` path still skips a `null` field entirely (leaves the value unchanged for every type).

### FIX-0629-5: Search and list with a null filter now return more than 50 rows

**Before**

A `POST /v1/{entity}/search` or `GET /v1/{entity}` request with a filter on an empty value (for example `{"filter": {"closedDate": null}}`) and a `limit` above 50 returned at most 50 records, even though `meta.total` reported the real match count and `meta.hasMore` was `true`. Auto-pagination silently stopped after the first page, so the common "read while rows equal `limit`" loop got an incomplete result with no error.

**After**

Such a request now returns up to `limit` records, the same as with any other filter. A `null` filter value is treated as "field is empty" consistently across every page of the result set.

**Impact on integrators**

Clients that paged manually via `offset` in steps of 50 to work around the truncation no longer need to — up to 5000 records can be fetched in a single call.

### FIX-0629-6: port change and deploy auto-routing now work out of the box on new app servers

**Before**

A regular "Publish app" server booted its agent with a fixed port, so `PATCH /v1/infra/servers/:id/port` and the deploy auto-routing step returned `409 PORT_NOT_APPLIED` (`NO_SCANNER`), and the public URL served the Black Hole service page while the app listened on a non-default port.

**After**

New regular app servers boot with port auto-detection: a service on any port is reachable through the tunnel immediately, and port change plus deploy auto-routing succeed. Agent and galaxy-host servers are unchanged.

### FIX-0629-7: app install via /v1/apps returns a precise error code instead of the generic BOX_APP_INSTALL_FAILED

**Before**

On an OAuth application install failure, [POST /v1/apps](/docs/apps/create) always returned `502 BOX_APP_INSTALL_FAILED`, with the full raw Bitrix24 response echoed into `error.message`.

**After**

The failure response is now classified: `403 B24_INSUFFICIENT_SCOPE` (the service integration lost its rights on the portal), `410 STALE_DEVELOPER_KEY` (access was changed or removed and cannot be auto-recovered), `502 RECOVERY_FAILED` (transient, retryable) or `502 DEVKEY_MINT_FAILED` (other). `error.message` no longer carries the raw Bitrix24 body — the diagnostic moves to the redacted `error.details.b24Body` field.

**Impact**

Existing "non-201 means install failed" handling keeps working unchanged. If your code branched specifically on `BOX_APP_INSTALL_FAILED`, add handling for the new codes above.

### FIX-0629-8: date-range search no longer returns empty for wide ranges

**Before**

`POST /v1/deals/search` (and likewise for leads, contacts, companies, quotes, invoices, items) with a date filter and a lower bound (`>=` / `>`) spanning more than 14 days returned `200` with an empty `data` and `meta.total: 0`, even when records existed in that range.

**After**

The request returns all matching records. `GET /v1/deals`, narrow ranges (≤ 14 days), and the `autoWindow: false` parameter were unaffected.

### NEW-0629-9: New error code CONNECTOR_APP_INSTALL_FORBIDDEN on app install

[POST /v1/apps](/docs/apps) on a self-hosted portal now returns `403` with code `CONNECTOR_APP_INSTALL_FORBIDDEN` when the Bitrix24 account administrator has forbidden the user from installing applications. The `error.message` field carries a clear localized explanation pointing the user to ask their Bitrix24 account administrator. Previously this denial surfaced as a generic `502 CONNECTOR_APP_INSTALL_FAILED` with no cause; that code is still used for other install failures.

### NEW-0629-10: Transfer bot ownership to another key

Added [POST /v1/bots/:botId/transfer](/docs/bots/management/transfer) — moves a bot's ownership to another API key of the same Bitrix24 account and the same user (or an account admin). Resolves the case where a bot is orphaned after the app is recreated: the owning key is revoked and the bot's runtime stops working. Body: `{ "targetApiKeyId": "<id>" }`. The target key must be active, in the same account, with the `imbot` scope. After the transfer, verify the new key's B24 binding via `POST /v1/bots/:botId/reauth`.
