# API changes: August 23, 2026

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

### BC-0823-1: OpenAPI no longer offers create-only fields in PATCH

> Old format supported until: not provided

**Before**

Fields accepted by the API only when creating a record appeared in `GET /v1/openapi.json` as ordinary PATCH body properties. A client generated from that schema could send them on update and receive `400 READONLY_FIELD`.

**After**

For entities with create-only fields, PATCH uses a separate input schema without those fields. Other entities keep using their existing PATCH input schema. In both cases, the POST input schema preserves the create contract.

**What integrators should do**

Regenerate the SDK from the new OpenAPI schema and use the type referenced by each PATCH `requestBody`. Affected entities use `UpdateInput`; other entities keep `Input`. Remove fields allowed only during creation from update payloads; POST continues to use `Input`.

### NEW-0823-2: several employees can work on one application's code

A server owner can now assemble a development team: employees of the account work with the server
using their own personal keys, with no key handover and no server rebinding. The Developer
role gets deploy, exec, upload, logs and the source depot; the Admin role additionally manages
the machine: lifecycle (stop, start, reboot, sleep schedule, repair), settings and security,
plan and disk, backups, the custom domain, the catalog card, the application audience and the
costs. Team membership, server deletion, access links and key rebinding stay with the owner.
Team membership is edited by the owner or a Bitrix24 account administrator on the Collaboration tab.

Access comes from membership, not from the key, so removing somebody from the team closes
their access immediately. The feature is gated by the `server-collaboration` flag.

A team member's deploy differs from the owner's in three ways. Environment variables are
preserved: the `env` field of their request is not applied (the response carries a warning),
and the variables already set on the application survive the deploy — including galaxy
applications, where every deploy used to wipe them. The Bitrix24 catalog card and the release
announcement stay with the owner: `displayName`, `description` and the `changelog`
announcement sent by a Developer are ignored with a warning, while the version note itself is
still stored in the source depot.

There is also an overwrite guard: the `baseVersionId` field in the body of
[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) ("this is the source version I
based my work on", e.g. `v12`). If a newer version has appeared in the depot, the deploy is
refused with `SOURCE_VERSION_STALE`, carrying the current version number and a download link —
instead of silently overwriting.

The declared version is checked for existence: a number ahead of the depot is refused with
`BASE_VERSION_NOT_FOUND` and the current version number — otherwise any non-existent number
would switch the guard off.

On a server whose owner has assembled a development team the field is required as soon as the
depot holds any version: a deploy without it answers `BASE_VERSION_REQUIRED` and names the
current version. A blind deploy over a colleague's changes is therefore refused even when
nobody knew about the guard. On a server without a team the field stays optional and a deploy
without it behaves exactly as before. So that the version number has somewhere to come from,
the version-download response now carries a `versionId` field next to the link — see
[Source code storage](/docs/source-storage).

A `409 EXEC_BUSY` refusal now names whoever is holding the server: `error.hint.holder` carries
the employee name, the kind of operation and when it started, and `error.hint.reason` begins with
that name. A background platform operation has no human behind it, so `holder.name` comes back
empty there. The field is additive — the existing fields of the refusal are unchanged.

A team member now sees the server in the API instead of working with it blind.
[GET /v1/infra/servers](/docs/infra/servers) also returns the servers whose development team
the caller belongs to: those rows carry an `access` block with `via: "collaborator"`, the
role, the allowed actions and the endpoints open to them, while the key's own rows carry
`access.via: "owner"`. `GET /v1/infra/servers/:id` answers a member with `200` and a reduced
card (no IP, no SSH access, no managing key; costs and the sleep threshold follow the role)
instead of the former `404`. `GET /v1/me` lists the memberships in `infra.collaboratorServers`.
Waking the machine, [POST /v1/infra/servers/:id/wake](/docs/infra/lifecycle/wake), is open to
the Developer role as well — the matrix promised it before, but the call answered `404`.
Searching for an employee to configure the audience,
[GET /v1/infra/servers/:id/b24-users](/docs/infra/access/b24-users), is open to the Admin role —
it used to answer a refusal even though that same role already configures the audience itself.

Additionally: a team admin can now perform, via their own personal API key, every server
operation their role allows — lifecycle control (stop, start, reboot, sleep, manual repair,
wake schedules, metrics), settings (SSH access, port, Bitrix24 event subscriptions, switching the
`BLACKHOLE`/`OPEN` mode via [PATCH /v1/infra/servers/:id/mode](/docs/infra/access/mode)), the
catalog card ([PATCH /v1/infra/servers/:id](/docs/infra/servers/update) — name and description),
and access audience (access policy, user/department access list). Previously these operations returned `403`/`404` via a
member's V1 key regardless of role — they only worked from the Vibecode dashboard. V1 membership
lists can now return a page: `GET /v1/infra/servers` accepts optional `page` and `limit`
and returns a true `total` alongside. Without the parameters the response is unchanged and
complete — the contract stays as published. In `/v1/me` the `infra.collaboratorServers`
block gained a `total` field.

Deleting source versions stays with the owner: `DELETE /v1/infra/servers/:id/sources/:versionId`
and the bulk `POST /v1/infra/servers/:id/sources/cleanup` answer a team member of any role with
`403 NOT_AUTHORIZED`. Listing, downloading, depositing and tagging remain open to them.

A member's deploy that must preserve the environment refuses to run when the environment cannot
be read: instead of silently deploying with an empty environment it answers
`502 GALAXY_PRESERVED_ENV_UNREADABLE` with a `retryable` marker. The application keeps running
its previous version.

A role refusal is now distinct from "no such server". A management operation not covered by
the member's role answers `403 SERVER_ROLE_FORBIDDEN`; `error.hint` carries their role, the
role required, the denied action and the endpoints that are open to them. For an unrelated
key the existence of the server stays hidden — it still gets a `404`.

An app whose owner has been deleted survives only while its team still has an admin — someone
who can take it over. A team of developers alone does not hold the app: nobody can manage it,
so it is cleaned up like any other ownerless app.

The `appUrl` field in [GET /v1/infra/servers](/docs/infra/servers), `GET /v1/infra/servers/:id`
and `GET /v1/me` (the `infra.collaboratorServers` block) no longer promises a team member a
link they are not allowed to open. Previously the field came back non-empty as soon as the
server had a subdomain, regardless of whether the application's audience actually let that
particular person open it — following such a link answered with a refusal. Now `appUrl` is
empty whenever the audience (`accessPolicy`) does not open the application to every account
employee and the member has no personal access grant. There is one exception: a member whose
access comes through a department grant does not see the link yet even though they could open
the application — for now the server's owner or team admin has to hand it to them directly.

### FIX-0823-3: platform public URLs no longer point away from the stand

**Before**

OAuth metadata (`GET /.well-known/oauth-authorization-server`) built `issuer` and the endpoint URLs from the `APP_URL` variable, which the deployment does not set for the backend, with a `http://localhost:3000` fallback. With that variable unset the document advertised an address no client can reach.

The welcome page at `GET /v1/me` had a different cause with the same outcome: it built every URL from the instance domain at once — both the images and the "Documentation" / "Home" buttons. On a stand those buttons led to the production dashboard.

**After**

OAuth metadata resolves through a shared helper that refuses a loopback value in production and falls back to the instance public domain.

The welcome page now keeps two addresses apart: images still load from the instance domain (otherwise they would not open), while everything clickable leads to the dashboard of the stand the page was opened from.
