# API changes: July 13, 2026

[← Changelog](/docs/changelog) · [July 2026](/docs/changelog/2026-07)

### FIX-0713-1: OAuth application key platform scopes now sync on create and edit

**Before**

A key issued together with an OAuth application via [POST /v1/apps](/docs/apps) did not receive the platform scopes (`vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`) that a key created in the dashboard gets. As a result [POST /v1/infra/servers](/docs/infra/servers) under such a key returned 403 `INFRA_SCOPE_REQUIRED`. Adding `vibe:infra` to the app's scopes via [PATCH /v1/apps/:id](/docs/apps) changed only the app, not the paired key — so it had no effect on access.

**After**

The paired key created via `POST /v1/apps` now receives the same default platform scopes as a key created in the dashboard. A `vibe:*` scope change via `PATCH /v1/apps/:id` (both add AND remove) now propagates to the app's active keys. A read-only (READONLY) key is rejected with 403 `WRITE_BLOCKED_READONLY_KEY` on three write operations: server creation (`POST /v1/infra/servers`), app scope change (`PATCH /v1/apps/:id`), and READWRITE key issuance (`POST /v1/apps` with `mode: "READWRITE"`).

**Impact on integrators**

Apps created via the API can now manage infrastructure without re-creation. A key whose app already declares `vibe:infra` while the key itself lacks it (the old divergence) is fixed with one edit: remove `vibe:infra` from the app's scopes and add it back via `PATCH /v1/apps/:id` — the second edit syncs the key; or re-create the app. A READONLY key can still create a READONLY app (`mode: "READONLY"`).

### FIX-0713-2: product-sections: unsupported filters return 400 instead of the whole table

**Before**

Operators (`>`, `<`, `!`, `%`, `$ne`, `$contains`, `$nin`) and non-exact-match fields (`sort`, unknown) in the filter of [GET /v1/product-sections](/docs/entities/product-sections/list) and [POST /v1/product-sections/search](/docs/entities/product-sections/search) were silently ignored — the code 200 and the whole list were returned unfiltered.

**After**

Such filters are rejected with `400 UNSUPPORTED_FILTER`. Filter by exact match or `$in` on `id`, `name`, `xmlId`, `code`, `catalogId`, `sectionId`. Sorting (`order`/`sort`) is unchanged.

**Impact on integrators**

Calls with operators or `filter[sort]` that previously returned 200 with unfiltered data now return `400` — switch to exact match or `$in`.

### FIX-0713-3: the reason for a failed first server provisioning is now visible

**Before**

If a server failed to come up on its first creation (a preemptible plan evicted, no free capacity), it silently ended up in `sleeping` status with no explanation. A client polling [GET /v1/infra/servers/:id](/docs/infra/servers/get) saw `sleeping` and could not tell why the deploy was blocked.

**After**

Such a server now moves to `status: "error"` with a populated `provisionError` (a human-readable reason) and a new `provisionErrorCode` field — a machine-readable failure category (`PREEMPTIBLE_EVICTION` / `PROVISION_TIMEOUT` / `NO_CAPACITY` / `GENERIC`). The `provisionErrorCode` field is added to [GET /v1/infra/servers/:id](/docs/infra/servers/get) and [GET /v1/infra/servers](/docs/infra/servers/list) next to `provisionError` (additive, `null` when there were no errors).

**Integrator impact**

No action needed: `error` is an already-existing status. Recover such a server via `POST /v1/infra/servers/:id/start` or `/repair` (not `/wake`: on `error` status it returns `422`; the `availableActions` field in the response points to the available action).

### NEW-0713-4: POST and PATCH /v1/tasks/:taskId/time accept createdDate

An optional `createdDate` field is now forwarded on [POST /v1/tasks/:taskId/time](/docs/entities/tasks/time/create) and [PATCH /v1/tasks/:taskId/time/:itemId](/docs/entities/tasks/time/update) — the time entry lands on the given date instead of the current moment (backfilling last week's tracks). ISO 8601 with offset, bare ISO, and `YYYY-MM-DD` are all accepted and passed to `CREATED_DATE` verbatim. When the field is omitted the behaviour is unchanged — the date equals the creation moment.

### FIX-0713-5: meta.hasMore no longer stays stuck at true with filter + offset

**Before**

When paginating a filtered list (for example [GET /v1/deals](/docs/entities/deals/list) with `filter` and `offset`), `meta.hasMore` stayed `true` at every offset — even well past `meta.total`. A `while (meta.hasMore) { offset += limit }` loop ran forever.

**After**

When Bitrix24 ignores an offset beyond the filtered set and returns the whole set, `meta.hasMore` is derived from the request window (`offset + limit < meta.total`) instead of being forced to `true`. At `offset=0` the response is unchanged; once the window passes `total`, `hasMore` becomes `false`. Applies to list and `POST /search` across all entities.

### FIX-0713-6: GET /v1/lists and /v1/lists/:iblockId/elements honour offset

**Before**

[GET /v1/lists/:iblockId/elements](/docs/lists/elements) and [GET /v1/lists](/docs/lists/lists) silently ignored `offset` — `?limit=50&offset=50` returned the same first page, and a client paginating by `offset` never reached rows 51 and beyond.

**After**

`offset` is mapped to `start`, the native pagination param for these methods, so `offset`-based paging works. An explicit `start` keeps priority when both are supplied.

### NEW-0713-7: New preserveEnv flag keeps .env across cleanDeploy

The `POST /v1/infra/servers/:id/deploy` body gained an optional boolean `preserveEnv` (default `false`). When `cleanDeploy: true` (which wipes the app directory including the `.env` file) and `preserveEnv: true`, the existing `.env` is read before the wipe and restored afterwards unless this request also passes `env` (a supplied `env` wins). Without the flag a re-deploy with `cleanDeploy` could boot the app with no environment variables — on its default port.

### NEW-0713-8: failed galaxy app builds now carry a `buildHint`

**Before**

A galaxy app build failure returned only `GALAXY_APP_BUILD_FAILED`
(and `GALAXY_APP_START_FAILED`) with a `buildLog` tail — you had to read the log
by hand to find the cause. `GET /v1/infra/servers/:id` for an ERROR app returned a
short `provisionError` but no ready recommendation.

**After**

The response now carries the parsed cause. The error body of
`POST /v1/infra/servers/:id/deploy` (502) additionally includes `error.category`
(a machine category such as `MODULE_NOT_FOUND`, `INSTALL_AUTH`, `RESOURCE`) and
`error.buildHint` — a localized recommendation line naming the concrete next step,
whenever the failure is classifiable. `GET /v1/infra/servers/:id` for an ERROR app
adds the same `data.buildHint` field. The fields are additive: when the cause is
unrecognized they are absent (`buildHint` is `null`), and `provisionError`/`buildLog`
keep arriving as before — existing requests are unaffected.

### FIX-0713-9: concurrent identical source saves no longer mint a duplicate version

**Before**

Two simultaneous saves of identical bytes to the same server (`POST /v1/infra/servers/:id/sources`, and the auto-save on deploy) could, under rare timing, create **two** byte-identical versions instead of one — content deduplication was best-effort.

**After**

Deduplication is deterministic: identical bytes submitted concurrently always converge on a single version. On `POST /v1/infra/servers/:id/sources` both responses return the same `versionId` and the losing request gets `deduplicated: true`; the deploy auto-save converges on that same single version (the deploy response shape is unchanged).
