# API changes: August 15, 2026

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

### BC-0815-1: combo runtimes with a database no longer slip into Galaxy silently

> Old format supported until: not provided

**Before**

Deploying an app into Galaxy with a runtime that promises a database (`node20-pg`, `node20-mysql`, `node20-redis`, `node20-mysql-redis`, `node20-pg-redis`, `node20-rag`, `python311-pg`, `python311-mysql`, `python311-redis`, `python311-rag`, `php83-mysql`) succeeded. The database suffix was stripped and the app got the language image only: no database engine, no connection variables (`DATABASE_URL`, `PG*`). The failure showed up on the first database call.

**After**

Such a deploy is refused up front: `400` with code `GALAXY_RUNTIME_DB_UNSUPPORTED`, a `details.suggestedRuntime` field (the language replacement — `node20` instead of `node20-pg`, for example) and a recovery hint. The catalog [GET /v1/infra/runtimes](/docs/infra/deploy/runtimes) now returns `supportedPlacements` on every runtime: `["standalone"]` — dedicated virtual machine only, `["standalone","galaxy"]` — Galaxy as well. The `name` and `packages` fields are unchanged: they describe the dedicated-machine install, where a combo runtime really does install the database.

**What integrators should do**

Filter the catalog by `supportedPlacements` containing `galaxy` before a Galaxy deploy. If a Galaxy app needs a database, take a language runtime and pass the connection string of an external database through `env`. If the database must live on the same machine, create a dedicated virtual machine (`placement: "dedicated"`, create without `source`) and deploy the original runtime to it in a second step. Apps already running are unaffected: the refusal only happens on a new deploy.

### NEW-0815-2: the bound placements list now states whether the account was checked

The [GET /v1/placements](/docs/apps/placements/list) response gained three optional fields. The `portalSync` field states how the reconciliation of the list against the Bitrix24 account ended: `ok` — the account returned every bound code, `drift` — the account did not return some of the codes, `unknown` — no reconciliation happened. The reason for a value other than `ok` is returned in `portalSyncReason` — `no_oauth_session`, `empty_vibe_list`, `b24_unreachable`, `unpublished` or `missing_on_portal`. On a divergence the codes that are listed as bound in Vibecode and were not returned by the account are named in `missingOnPortal`, and the same codes get a line in `warnings`.

An application withdrawn from the catalog is not treated as a divergence: the `unpublished` value states that the platform itself removed the placements from the account and kept the codes for republishing. The reconciliation runs when a session token is passed together with the application key and the application has bound placements. Without those conditions `portalSync` is returned as `unknown` — the response behaviour did not change, it is simply stated explicitly now.

Previously the outcome had to be inferred from the presence and the length of the `handlers` array, which did not separate "the account answered and the code is not in the answer" from "the account response could not be obtained": both produced an empty array. The `handlers` field is now absent in the second case, and the reason is visible in `portalSyncReason`. Existing calls keep working, the new fields are additive.

### FIX-0815-3: AI quota is resolved by plan in every Western zone

**Before**

A Bitrix24 account plan is recorded as "account zone plus edition" — for example
`jp_pro100`. The platform stripped the zone prefix for only ten of the Western zones,
so an account in the `cn`, `id`, `it`, `vn`, `jp`, `ms`, `th`, `hi`, `co` or `ae` zone
matched no plan setting at all. Its monthly AI quota was then sized by the fallback
rule instead of by its own plan.

**After**

The prefix is stripped in all twenty-one Western zones, and the quota is sized by the
setting of the plan the account actually holds. No client-side change is required; for
affected accounts the monthly quota size changes from the next billing period — up or
down, depending on how their plan is configured.

### NEW-0815-4: an app can be added to the Bitrix24 catalog with a dedicated call

A new call is available — [POST /v1/infra/servers/:id/b24-catalog/publish](/docs/infra/servers/b24-catalog-publish). It creates the app card in the Vibecode apps catalog on the Bitrix24 account. Previously the card appeared only after a successful deployment, so an app brought up any other way was missing from the catalog, and granting access to people did not surface it.

[GET /v1/infra/servers](/docs/infra/servers/list) and [GET /v1/infra/servers/:id](/docs/infra/servers/get) now return a `b24CatalogSync` block with `status`, `itemId`, `attempts`, `pendingOp` and `eligible`. A non-null `itemId` is the "the app is in the catalog" signal. The `eligible` field answers a different question — whether a card can exist at all: an agent runtime, a galaxy host, a server without a subdomain and a server with no app deployed to it never get one.

Existing calls keep working unchanged: editing the access policy or the access list still does not create a card, it updates an existing one.

### FIX-0815-5: the Cowork monthly limit now follows the tier grid without waiting for a renewal

**Before**

`/v1/cowork/state` and `/v1/cowork/me` measured a paid seat's monthly window
against the volume frozen when the seat was bought. When a platform
administrator raised a tier's monthly limit, the new value reached the seat only
at its next renewal: `month.pctUsed` did not move, and a seat sitting at
`exhausted` stayed blocked until the paid period ended even though the tier
limit had already grown. The weekly and five-hour windows updated immediately,
so the three windows of one seat answered by different rules.

**After**

A paid seat's monthly window is measured against the greater of two values: the
live tier limit and the volume sold for the current period. A raise applies at
once — `month.pctUsed` drops with no request made, and `month.exhausted` returns
to `false` when the new limit exceeds what was spent. A cut does not touch the
paid period: until it ends the seat is measured against the volume sold, and the
new value takes effect from the next period. A free seat is still measured
strictly against the live limit.

The response still carries no absolute numbers — only the shares, the
`exhausted` flag and the `resetAt` moment change. A client caching
`month.pctUsed` should re-read the state before deciding to block, rather than
assuming the share only ever grows.
