For AI agents: markdown of this page — /docs-content-en/changelog/2026-08-06.md documentation index — /llms.txt

API changes: August 6, 2026

← Changelog · August 2026

NEW-0806-1: 402 for an exhausted Cowork/Code quota carries a Retry-After header

The 402 response with code cowork_quota_exhausted on POST /v1/chat/completions now carries a Retry-After header — the number of seconds until the exhausted quota window (5h, week or month) resets. Previously the reset moment was visible only in the resetAt field of the response body; the header is understood by plain HTTP clients without parsing the body. The 402 response with code insufficient_balance does not carry the header — an empty balance has no reset time.

FIX-0806-2: Web search: the response status distinguishes a provider key rejection from a provider failure

Before

Every search provider error on POST /v1/search arrived as 502 UPSTREAM_ERROR — a provider rejecting the key (401/403), provider throttling (429), and a genuine failure looked the same. Clients retried requests that could never succeed.

After

For a BYOK key, provider 401 and 403 responses keep their status — the provider rejected your key, replace it. A provider 429 keeps its status for any key and carries the Retry-After header. The error code stays UPSTREAM_ERROR in every case, and the body additionally carries the upstream_status field with the provider's original status. Every other provider error, including a rejected platform-engine key, still arrives as 502. Additionally, the message field of the 400 INVALID_REQUEST response is now length-capped — the received value is no longer echoed in full.

Impact on integrators

A handler that retried on any 5xx keeps working. If you branched on 502 as "any provider error", add branches for 401/403 (replace the BYOK key) and 429 (retry per Retry-After); the reliable "this is a provider error, not an authorization error" signal is the upstream_status field in the body.

FIX-0806-3: the operation registry in GET /v1/guide now lists what actually works

Before

An entity's operation list in GET /v1/guide disagreed with the set of working endpoints in both directions.

It stayed silent about working operations. No entity declared fields, although GET /v1/{entity}/fields answers for 46 of 49 entities. Bookings were missing list and search, although GET /v1/bookings and POST /v1/bookings/search are served by dedicated handlers — a robot read the entity as write-only and had no way to obtain an identifier. Open-channel configs were missing list, search, create, update and delete, leaving only getById, aggregate and batch in the registry. The same mechanism hid create for document templates, list and create for addresses, search for org-structure nodes and delete for a user, while the address search was described with the generic windowed-search contract its handler does not implement.

And it promised what an entity does not have: the batch example named the create action for eight entities, where that action answers 400 ACTION_NOT_SUPPORTED.

After

An operation is declared exactly when its route is really registered. Added: fields for the entities that serve that route, list and search for bookings (with the mandatory dateFrom and dateTo parameters stated in the description), the full list / search / create / update / delete set for open-channel configs, create for document templates, list / search / create for addresses, search for org-structure nodes and delete for a user. The descriptions of these operations list the parameters their own handler reads rather than the generic search contract: windowed search (autoWindow, windowCount) does not exist for them.

The batch example names an action the entity accepts, and the key that action reads: ids for delete, items for other writes, calls for reads. An entity with no available write operation gets a read example.

The note on such an entity no longer states the read set as one fixed sentence ("accepted: list, get, fields"): it names the actions that really answer with data for THIS entity, and separately what the envelope does with the rest. There are three reasons an action stays blind: on an entity with no field-schema method fields answers an empty object (and the note points at GET /v1/{entity}/fields when that route exists); on an entity with no addressed read method get answers the collection's first record rather than the one asked for; on a REST 3.0 entity the batch sub-call does not reach the method at all and comes back as a per-call error inside the 200; and on the open-channel config list the sub-call reaches Bitrix24 without the envelope imopenlines.config.list.get requires, answering 200 with the whole collection while silently dropping the filter.

The same statement is corrected in the two other places a client sees it: the 400 ACTION_NOT_SUPPORTED body no longer ends in "Supported batch actions: list, get, fields" (it now carries the same computed set — previously a client that read the honest registry and then tripped the refusal got the misleading list back), and the OpenAPI description of the open-channel config batch read now says which of the accepted actions answer with data.

The same descriptor's select description is corrected too: for the open-channel config list the comma-separated form (?select=id,name) IS read — only the indexed form (?select[0]=id) is not.

An entity that does not have an operation still does not get one: fields did not appear for task comments, for calendar sections or for mail mailboxes. What should not be declared stays undeclared too: routes that exist only to refuse a call and point at the correct path, and aggregate on an entity where only counting works — the search description still names that path.

Impact on integrators

The change is additive: existing operations fields are neither renamed nor removed. A client that built its list of available calls from this registry now sees operations it previously had to guess or look up in the documentation. A client that copied the batch example verbatim will stop receiving 400 ACTION_NOT_SUPPORTED on read-only entities.

FIX-0806-4: the task service fields are accepted on writes — exactly as Bitrix24 itself accepts them

A task carries seven service fields: the creator (createdBy), who changed it (changedBy), who closed it (closedBy), who changed its status (statusChangedBy), and the creation, change and closing dates (createdDate, changedDate, closedDate). Bitrix24 accepts and stores all of them — both when a task is created and when it is updated. Vibecode refused six of the seven, which made it stricter than the platform for no gain.

Before

POST /v1/tasks and PATCH /v1/tasks/:id answered 400 READONLY_FIELD for changedBy, closedBy, statusChangedBy, createdDate, changedDate, closedDate and never reached Bitrix24. The upper-case spellings were refused the same way — CHANGED_BY and the rest. The refusal also came from a POST /v1/batch sub-call. The seventh field, createdBy, worked on creation and was refused on update.

After

All seven are accepted on both operations and on all three write surfaces — the single route, the entity batch request and the global batch request. Both spellings, createdBy and CREATED_BY, are accepted. The value is applied within the permissions of the calling user: when Bitrix24 refuses to edit the task, the refusal arrives as it is — a 422 carrying its own text, with no substitution by an error of ours and no false success.

A change of the creator is written to the task change log, and the real calling user stays visible there. For the other six fields no log entry exists. One more subtlety — for the three dates, a value without a timezone gets the offset from the X-Vibe-Timezone header when sent as createdDate, while as CREATED_DATE it goes through unchanged. Both subtleties are covered on the PATCH /v1/tasks/:id page.

What did NOT change: id is still refused — Bitrix24 assigns the identifier itself and ignores a submitted value, so an explicit refusal is more honest than a silent loss. dateStart, activityDate and realStatus stay closed as well, but for a different reason: their behavior on write was not verified, and we will not declare a field open without verifying it.

Pass an existing employee only — in all four fields that carry a user id. Bitrix24 does not check the value for existence and will store any number, and a task whose creator does not exist stops being manageable through the API: a further update and a deletion are both refused, even for an administrator key and even directly in Bitrix24, bypassing us. The warning is on the task-update page.

Impact on integrators

Nothing to change: requests that used to be refused now go through. If your code treated 400 READONLY_FIELD as protection for authorship and history, it never played that role — Bitrix24 itself accepts the same values through its own interface, bypassing our layer. Only the Bitrix24 permission model can restrict overwriting the service fields — that is a separate change on the platform side. For leads and deals the author field stays closed, which has not changed: Bitrix24 silently ignores the value there, so an explicit refusal remains the honest answer.

FIX-0806-5: a catalog application card opens its subpath instead of the server root

Before

An application card in the Bitrix24 catalog always opened the root of its Black Hole server. An application serving its interface from a subdirectory could not be opened from the catalog at all: the click answered HTTP 200 and rendered whatever lives at the root of the same server. The application address (appUrl) had no effect on this, and editing it through PATCH /v1/apps/:id never reached the card.

After

The card now opens the full address of the linked application — subpath, query and fragment included — whenever that address points at the same Black Hole subdomain as the card's server. Everything else keeps the previous server root: a different subdomain, a custom domain, a different scheme, an empty or unparseable address.

Editing appUrl through PATCH /v1/apps/:id now queues the card for an update, so the new address reaches Bitrix24 on its own. Already-published cards are reconciled platform-side — no integrator action required.

Integrator impact

Nothing to change. An application serving its interface from the root behaves exactly as before. An application in a subdirectory no longer needs a manual workaround — it is enough for appUrl to carry the subpath.

FIX-0806-6: speech recognition now reports a temporary provider pause

Before

When the cluster was temporarily unavailable, POST /v1/audio/transcriptions could respond with 502 ai_provider_unavailable without telling the client how long to wait before retrying.

After

In this state, the endpoint responds with 429 ai_provider_cooldown and a Retry-After header in seconds. The request is not executed and consumes neither quota nor money. Wait for the stated interval and retry the same request.

FIX-0806-7: speech recognition now really waits the stated 15 minutes

Before

For a long recording, POST /v1/audio/transcriptions could answer 503 ai_provider_timeout after about 5 minutes, even though the endpoint documents a wait of up to 15 minutes. The error text described a network-layer timeout.

After

The endpoint waits the full stated period — up to 15 minutes — and answers 503 ai_provider_timeout with a Retry-After header only once it expires. The limit on recording length is unchanged: for files longer than ~30 minutes, keep splitting the recording into parts.

FIX-0806-8: galaxy app exec now targets the container by its real name

Before

POST /v1/infra/servers/{id}/exec on a galaxy app always addressed the container by its subdomain. An app restored from a clone runs under a different name, so the command targeted a container that does not exist — the call failed, and in the worst case it could reach a leftover container from an earlier deployment. The rest of the galaxy lifecycle (deploy, migrate, stop) already accounted for the rename; only exec did not.

After

The container name is resolved by one shared rule for every operation: the on-host name when the app was renamed, otherwise subdomain. The resolved name is validated before it is interpolated into the command; an unusable name returns 409 GALAXY_APP_NOT_READY with invalid on-host name instead of running anything. Apps that were never restored from a clone are unaffected.

BC-0806-9: V1: server status in JSON is always lowercase

Old format supported until: 06.09.2026

Before

GET /v1/infra/servers and GET /v1/infra/servers/:id returned a lowercase status (running, sleeping), while POST /v1/infra/servers/:id/wake, POST /v1/infra/servers/:id/refresh, the currentState.status field on 422 responses of POST /v1/infra/servers/:id/start, POST /v1/infra/servers/:id/stop, POST /v1/infra/servers/:id/reboot, and infra.unhealthyServers[].status in GET /v1/me exposed the stored enum value in UPPERCASE (RUNNING, SLEEPING, PROVISIONING). A client that learned status === 'running' from the docs and GET broke on the wake and refresh responses.

After

Every listed field of the public V1 JSON carries a lowercase server status: provisioning, running, stopped, sleeping, error, deleted. The refresh data field is still a string, not an object: compare data === 'running', not data.status. The blackholeStatus field is unchanged — it stays UPPERCASE (CONNECTED, DISCONNECTED, NONE).

Impact on integrators

Replace equality checks against 'RUNNING' / 'SLEEPING' / 'PROVISIONING' and the other uppercase values with lowercase ones, or compare case-insensitively. Read the status from the structured fields (data, currentState.status) rather than from message / userMessage prose, where an uppercase status may still appear.

Previously a galaxy app accepted an inline archive only: source.url and source.versionId were rejected with 400 GALAXY_DEPLOY_CONTENT_ONLY. POST /v1/infra/servers/:id/deploy now accepts both forms where the platform has enabled link deploys for your Bitrix24 account; where it has not, GALAXY_DEPLOY_CONTENT_ONLY is returned as before, and the inline source.content keeps working in all cases.

A link is downloaded by the host itself, so the archive never travels through the request body: the inline size limit (413 GALAXY_UPLOAD_TOO_LARGE) does not apply to this path, and it does not consume a concurrent large-request slot (429 DEPLOY_BACKEND_BUSY). source.versionId deploys a version already held in source storage: the platform mints the signed link itself and links that version to this deploy, so the history shows exactly what went to production.

On a galaxy app the link points at source storage only — the signed link of a saved version qualifies. Any other address is refused with 400 GALAXY_SOURCE_URL_NOT_ALLOWED, so a large archive is first saved as a version and then deployed by source.versionId. A separate virtual machine has no such restriction.

Creating a server with a source (POST /v1/infra/servers) accepts source.url on the same terms. source.versionId is not accepted there: at create time there is no server yet whose storage would scope the version lookup — deploy a saved version as a second step, via /deploy.

The dashboard routes keep accepting the inline archive only.

FIX-0806-11: the smart-process items field reference no longer shows the contacts field

Before

GET /v1/items/:entityTypeId/fields listed a contacts field of type crm_contact. No value for it ever arrived, either in the list or in the item card, and it could not be written: Bitrix24 accepted an empty array only and rejected any non-empty value with an error from its own internal data layer. The field reached the reference through the passthrough of the Bitrix24 schema; the platform never declared it.

After

The field is gone from the reference. Linked contacts are read and written through contactId and contactIds, which are unchanged. Filtering and sorting by contacts are still refused with UNKNOWN_FILTER_FIELD and UNKNOWN_SORT_FIELD.

Impact on integrators

No action needed: the field never had a value, so a client reading it always got nothing. If you generated a data model from the reference, drop contacts from it and rely on contactIds.

FIX-0806-12: the pages field reference now says which fields can be empty

Before

The GET /v1/pages/fields response gave no way to tell a field that always has a value from a field that arrives as null. Two descriptions also promised something other than what arrives: datePublic was described as "arrives as an empty object", and dateCreate, dateModify and datePublic as dates in a fixed template. A client that wrote its parsing against those descriptions tripped over an empty value, and a date filter in the wrong format returned an empty list with code 200.

After

Nine fields Bitrix24 does not always fill are marked with a nullable flag: description, xmlId, tplId, tplCode, folderId, searchContent, initiatorAppCode, rule, datePublic. The set was measured over the whole page collection of a live account rather than derived from the Bitrix24 method reference.

datePublic is described honestly: the wrapper returns null, and that is the usual value even for a published page, so read active or public to tell whether a page is published. The descriptions of dateCreate, dateModify and datePublic no longer promise a fixed template: the value is a string in the account locale format, identical in the list and in the card. The filter needs the same format: a value in another locale's format, or in ISO, is not recognized by Bitrix24 and returns an empty list with code 200.

The same fields are marked in the generated OpenAPI schema, where the type is now written as ["string", "null"], so a client validating the response against the schema no longer fails on an empty value. The write contract is untouched.

Impact on integrators

No action needed: the field set, the types and the values are unchanged — only the nullable flag was added and the descriptions were made accurate. If you were telling whether a page is published by the presence of datePublic, switch to active or public.

NEW-0806-13: the key-limit refusal now states the numbers

On a KEY_LIMIT_REACHED (409) refusal, POST /v1/apps now puts the quota state into error.details: limit — how many keys per person the Bitrix24 account administrator allows, used — how many are taken right now.

Before

JSON
{
  "success": false,
  "error": { "code": "KEY_LIMIT_REACHED", "message": "Maximum number of API keys reached" }
}

After

JSON
{
  "success": false,
  "error": {
    "code": "KEY_LIMIT_REACHED",
    "message": "Maximum number of API keys reached",
    "details": { "limit": 10, "used": 10 }
  }
}

The field is additive — clients reading only code see no change. used also counts keys the platform issued itself (apps, agents, bots), so it can exceed the length of the GET /v1/keys list.

NEW-0806-14: the site and employee field maps now carry labels, descriptions and value lists

GET /v1/sites/fields now returns a label and a description on all 22 fields — previously only the type field had them, and the other 21 arrived with nothing but a type and a read-only flag. The descriptions say what the type cannot: that active is not settable through the API, that code is stored in a slash-wrapped form, that landingIdIndex/landingId404/landingId503 are settable on update only, and that dateCreate and dateModify arrive as a string in the Bitrix24 account locale format rather than ISO 8601.

GET /v1/users/fields gained an enum of allowed values on gender (personalGender: M, F) and on account type (userType: employee, extranet, email), each value with its own label. Labels and descriptions also appeared on ten work-details fields that had no label in Bitrix24 at all, where the field name used to arrive in place of one: WORK_FAX, WORK_PAGER, WORK_STREET, WORK_MAILBOX, WORK_STATE, WORK_ZIP, WORK_COUNTRY, WORK_PROFILE, WORK_LOGO, WORK_NOTES. When the Bitrix24 account labels such a field itself, its own label is kept unchanged.

Gender also gained a nullable: true flag, and in the OpenAPI schema the property type is now declared as ["string", "null"]. An unfilled gender comes back empty (null) — 48 of 50 employees on the measured Bitrix24 account answered that way — while the schema without the flag promised a string and nothing but a string, so a client validating the response against our own published schema failed on almost every record. The flag describes the read only: in the request-body schema the field type is still a plain string.

The value lists also arrive on the two other machine-readable surfaces — GET /v1/guide (the entity's fieldsDetailed block) and the OpenAPI schema (x-enumValues on the property). Labels and descriptions of declared schema fields are carried by OpenAPI alone (title and description on the property), so a schema-generated client picks them up with no extra calls; the guide does not carry labels, by design. Labels and descriptions of the ten work-details fields arrive in the field map itself only.

The change is additive: responses gained new keys while the field set and the values stay the same, so existing integrations keep working untouched.

FIX-0806-15: product sections: the sort field is marked read-only and not-returned

Before

GET /v1/product-sections/fields advertised sort as writable, POST /v1/product-sections and PATCH /v1/product-sections/:id accepted it without an error, and Bitrix24 did not save the value. Meanwhile no read response — the card, the list, search, the create echo — carried the field, even when it was requested explicitly in select. The client got a success and went on believing the order had been set.

After

The field is marked read-only and notReturned: true. Sending sort in a create or update body is refused with 400 READONLY_FIELD before the Bitrix24 call; the field stays visible in the field map together with a description of the reason, so it can be read on the spot. Ordering by it works as before: ?sort=sort&order=asc and order=desc give a different order. Filtering by sort is still refused with 400 UNSUPPORTED_FILTER.

Impact on integrators

Remove sort from product-section create and update bodies — otherwise the whole request now gets 400 READONLY_FIELD instead of the former success. If your code read sort out of a response, it was never there: the value came back undefined. Change the order of sections in the Bitrix24 interface, and read the order by sorting the list on that field. There is no parallel support for the previous behaviour: the previous behaviour was that the value was silently lost, so there is nothing to keep.

FIX-0806-16: the active field of a site is read-only now — activation goes through publishing

Before

GET /v1/sites/fields described active as an ordinary writable field, and a request carrying it went through: POST /v1/sites and PATCH /v1/sites/:id answered with a success. The value was dropped. Bitrix24 accepts ACTIVE neither in landing.site.add nor in landing.site.update — their contract does not declare the field, and a new site is always created inactive. A live round-trip on both verbs confirmed the loss: a create with active: true and an update to active: true both answered with a success while the flag stayed off.

After

active is marked readonly. Passing it in a create or update body is refused with 400 READONLY_FIELD before the Bitrix24 call. The field stays in the list/get response and in the /fields directory — reading it is unchanged, filtering and grouping by it included.

A site is activated by publishing it in the Bitrix24 account interface.

Impact on integrators

If your code passed active in a site create or update body, drop it. The value was never stored anyway, but now the whole request is refused, so the rest of the body is not applied either — title, code, domain, description. There is no parallel support for the previous behaviour: the previous behaviour was that the value was silently lost, so there is nothing to keep.

FIX-0806-17: base image registry unavailable during a galaxy app build is now transient, not fatal

Before

When the public image registry was unreachable at build time, POST /v1/infra/servers/:id/deploy returned 502 with a raw Docker message and the app was marked broken — the retry had to be issued by hand and the response carried no hint.

After

The response carries the GALAXY_BASE_IMAGE_UNAVAILABLE code, retryable: true and a hint telling you to re-send the same request in 2-3 minutes. The slot is not marked broken, so the retry lands on it. For agents the platform retries on its own. One-shot create-with-source (POST /v1/infra/servers with source) no longer holds the HTTP response, so there the app is still marked broken — but the error text names the cause and asks you to re-deploy.

FIX-0806-18: issuance refusals now name a service problem instead of an access one

Before

When Bitrix24 refused key issuance or app installation, the answer was chosen from a stale local snapshot of the account's marketplace state. An account whose entitlement was in force could still receive an access-paywall answer with a purchase call to action — while nothing was actually missing on the account side.

After

While the account's marketplace entitlement is in force, an issuance refusal is returned as 502 CONNECTOR_REST_UNAVAILABLE with a "try again / contact support" message and no purchase call to action. The response carries error.details.reason (the original refusal reason) and error.details.retryable: true. When the entitlement really is missing, the access answer is unchanged. Affects POST /v1/apps and the matching in-product routes for key issuance and app creation. Additionally, a "REST unavailable" refusal is now retried once automatically, which clears the race right after an entitlement is granted.

FIX-0806-19: the platform now passes the application its port in the PORT variable

Before

The platform agreed with itself about the application port on three levels — public traffic forwarding, the image EXPOSE, and the healthcheck — but never told the application. An application written to the common cloud-platform convention (listen(process.env.PORT)) read an empty value, bound a random free port, and nothing listened on the expected one: the gateway served "application not found" even though POST /v1/infra/servers/:id/deploy reported success.

After

The platform passes the port number in the PORT environment variable — always equal to the request's port field (3000 by default). On a dedicated virtual machine this is a separate platform file .vibe-platform.env that the systemd unit loads after your .env; your .env is neither read nor rewritten. In a galaxy application PORT arrives in the container environment at start. The response gained a platform_env step.

The PORT key is now reserved by the platform: if you pass your own env.PORT that differs from the port field, the platform overrides it and says so with a line in the response warnings[]. Passing a matching value is fine — there will be no warning.

Impact on integrators

In the normal case there is nothing to change: an application listening on process.env.PORT now works without an explicit env.PORT, and already running applications receive PORT on their next deploy. One exception is worth checking: if you kept something other than your own listen port in env.PORT (a database port, an upstream service port), rename that variable — PORT now belongs to the platform and your value no longer reaches the application. The deploy response carries a warnings[] entry when that happens. If you read the .env file directly instead of the process environment, read process.env.PORT — the platform value lives in a separate file. With your own systemd unit (systemd: false) the platform file is written but YOU load it — until you do, your env.PORT keeps winning, and the response warning says exactly that; POST /v1/infra/servers/:id/deploy describes how to load it.

BC-0806-20: quota consumption is reported as percentages only

Old format supported until: 06.02.2027

Before

GET /v1/ai/quota returned absolute consumption counters in data.byModel[] — tokensIn, tokensOut and audioSeconds — alongside the pctOfLimit share.

JSON
{ "modelId": "bitrix/bitrixgpt-5.5", "calls": 1240, "tokensIn": 800000, "tokensOut": 350000, "audioSeconds": 0, "pctOfLimit": 1.2 }

After

The three fields are gone. Quota consumption — like the limit itself — is exposed only as a relative value: pctOfLimit (the share of the monthly limit consumed by the model) and calls (the call count).

JSON
{ "modelId": "bitrix/bitrixgpt-5.5", "calls": 1240, "pctOfLimit": 1.2 }

What integrators should do

There is one authoritative figure for the account — data.pctUsed: it is computed from the charge ledger and accounts for the time-of-use discount. The byModel[].pctOfLimit breakdown shows WHERE the quota went and is computed by repricing the call journal at today's prices, so there is no need to add the per-model shares up and compare the sum with pctUsed — the two will differ. If you need token counts for your own accounting, take them from GET /v1/ai/usage or capture them at call time — the POST /v1/chat/completions response still carries the usage block with prompt_tokens and completion_tokens.

FIX-0806-21: an exhausted Cowork/Code quota is no longer served by a substitute model

Before

When the Cowork/Code quota ran out, a request from a desktop key was served by the reserve model with the request's tools and system prompt passed through unchanged. The model answered fluently and could report work it had not done. The response carried HTTP 200 and X-Cowork-Fallback: true.

After

One behaviour for every key: tools, tool_choice and response_format are stripped, and the model states that the limit is reached and when it resets. HTTP 200 when a reserve model is configured, otherwise 402 cowork_quota_exhausted as before. The X-Cowork-Fallback: true header and the COWORK_QUOTA_FALLBACK warning remain, but now mean "the limit was announced", not "the request was served by another model".

Impact on integrators

Do not expect tool_calls or a structured response on an exhausted quota: response_format is stripped, so you get prose, not JSON. Detect the state via the X-Cowork-Fallback header, the COWORK_QUOTA_FALLBACK warning, or the 402. Separately, the monthly reset date on the free plan is fixed. resetAt.month (GET /v1/cowork/me), windows.month.resetAt and subscription.currentPeriodEnd (GET /v1/cowork/state) used to return the date stored on the subscription row, and on a free seat that date stopped moving once the period ended — so it arrived in the past and the countdown read "less than a minute" forever. All three now return the period the seat is actually in: the monthly counter resets on the first request after the period ends. In the 402 cowork_quota_exhausted body, the month window's resetAt is no longer 1970-01-01.

Impact on integrators

If you cached a free seat's currentPeriodEnd as a fixed date, re-read it: on an overdue seat it moves forward.

FIX-0806-22: server repair now reports why it failed and no longer leaves the agent stopped

Before

On failure GET /v1/infra/servers/:id/repair-status returned an error with no cause — SSH install failed (exit 255); serial fallback: Serial console install failed. That text could not distinguish a closed port from an unreachable machine or from a download that never completed. The agent install also stopped the running service BEFORE downloading the replacement: if the download failed (no outbound connectivity, unreachable download host), the agent stayed stopped and the next repair attempt repeated the same sequence.

After

error now carries the cause: the SSH message for the regular path (Connection refused, Connection timed out and so on), and a short excerpt of the console output for the emergency-console path (for example curl: (6) Could not resolve host: …). The excerpt is stripped of secrets and length-bounded. The install downloads the new agent first and only then stops the service, and brings the agent back up if any later step fails.

FIX-0806-23: key issuance now checks platform access

Before

POST /v1/keys and POST /v1/apps issued a new key to any authenticated account, including accounts whose access to the platform was closed. The key then worked — issuance never consulted the access check.

After

Access is verified before the key is issued. An account without access is refused with INT_TARIFF_REQUIRED, the same code it already receives on other surfaces.

Keys already issued keep working. Key rotation, automatic recovery and ownership transfer are unaffected: an account whose access lapsed must still be able to wind its own affairs down.

Impact on integrators

Handle the refusal on issuance the same way it is handled on the other surfaces: restore access and retry. Keys issued earlier need no changes.

NEW-0806-24: a clear refusal when a personal key is left with only placement or entity

A personal key is backed by a Bitrix24 incoming webhook, and that surface does not store the placement and entity scopes — they require an application context. Such a request used to fail opaquely: key creation returned 502 DEVKEY_MINT_FAILED advising the caller to contact the account administrator, and a scope edit returned 502 DEVKEY_SCOPE_SYNC_FAILED.

Both cases now answer 400 PERSONAL_KEY_WEBHOOK_SCOPES_INVALID with a message that says what to do: add at least one regular scope (for example crm or user_brief), or create an OAuth application if you need placements.

The refusal fires only when dropping those two scopes leaves nothing Bitrix24 can bind to the webhook. A mixed set (placement + crm) still succeeds.

Affected endpoints: POST /v1/keys, PATCH /v1/keys/{id}, POST /v1/keys/{id}/rotate

Related response change: for a personal key, the scopes field of a create or update response no longer echoes placement and entity — the webhook never carried them, and the response used to promise a scope the key does not have. App keys and system keys are unaffected.

FIX-0806-25: a Galaxy Python build no longer fails on a version that exists

Before

When a runtime: python311* deploy could not reach the package index, No matching distribution found for <package> was all you got — for a version that exists and installs fine. The platform showed nothing next to that text, so it read as your mistake.

After

The default package index on this platform is the canonical PyPI, and that has not changed. Your own --index-url in requirements.txt or in the install command still wins over ours.

If the index still does not respond, error.category is now INSTALL_REGISTRY_UNAVAILABLE (was GENERIC), and error.buildHint — plus the field of the same name on GET /v1/infra/servers/:id — carries a readable reason instead of nothing. The value is additive: existing codes are unchanged. The failure is terminal — the platform does not retry it for you.