For AI agents: markdown of this page — /docs-content-en/changelog/2026-08-03.md documentation index — /llms.txt
API changes: August 3, 2026
FIX-0803-1: reopening an archived ticket now clears the resolution stamp too
Before
Reopening a ticket from ARCHIVED into an active status (NEW/REVIEWING/AWAITING_USER/NEEDS_REVIEW) — via PATCH /v1/feedback/:id or POST /v1/feedback/:id/comments — did not reset resolvedAt/resolvedBy when the ticket had been resolved and then archived. On reads (GET /v1/feedback/:id) such a ticket looked both active and resolved. The clear only fired for RESOLVED/WITHDRAWN sources.
After
ARCHIVED joins RESOLVED/WITHDRAWN: reopening out of any closed status into an active one clears resolvedAt/resolvedBy. On the PATCH path resolution is cleared too (including the archive reason) — an active ticket carries no resolution; an explicit resolution in the same request still wins. On the comment path resolution equals the comment body. Moving into ARCHIVED still preserves the stamp (archiving keeps resolution history).
FIX-0803-2: missing attachment bytes now answer 404 instead of a truncated response
GET /v1/feedback/{id}/attachments/{attId}/file and .../thumb now confirm the bytes exist before any header is sent. When the attachment record is present but its bytes are not in storage (after manual cleanup or a cascading delete), the answer is a plain 404 NOT_FOUND.
Before
The response opened as 200 and then broke off mid-body: the client received a truncated image or an empty stream under an already-sent success status, indistinguishable from a slow network.
After
404 { "success": false, "error": { "code": "NOT_FOUND", "message": "Not found" } } — the same code these routes already return for someone else's or a deleted attachment. Clients that already handle 404 here need no changes.
The change accompanies moving attachment files into object storage: serving an attachment no longer depends on which machine accepted the upload. Response shapes and route paths are unchanged.
FIX-0803-3: server creation now says plainly when it returned the application's existing server
When the calling key belongs to an application whose server slot is already filled, POST /v1/infra/servers returns that server instead of creating a new one. That was already the behaviour, but the response gave you almost nothing to notice it by: the only signal was an undocumented reused field, and the name in the response belonged to the existing server rather than the one you asked for.
Such a response now carries the full disclosure: data.reusedReason with the value APPLICATION_ALREADY_HAS_SERVER, data.requestedName echoing the name you sent (always, even when it equals the existing name), and a warnings array next to data with at least one entry naming the existing server and stating that deploying replaces the code currently running on it. The reused and deploying fields are now declared in the schema and in the documentation.
The second silent loss is disclosed the same way: a reuse does not apply the displayName and description you sent — the server keeps its own name and description. The response now says so through data.metaIgnored and a dedicated warning; rename the server deliberately with PATCH /v1/infra/servers/:id.
When the request carried a source that cannot be built onto the reused server (the galaxy application already has a live container, or the server is a dedicated virtual machine), the archive is discarded — and the response now says so through data.sourceIgnored and a dedicated warning. It used to be discarded silently.
On a reuse response data.next now arrives only when there is nothing to overwrite. It used to arrive on every two-step reuse, including one that returned a server already running someone's code — so the machine-readable "next step: deploy" contradicted the hint in the same body. When the server may be running code the field is deliberately absent: confirm the server is the right one first. A missing next is not an error.
Two existing fields changed meaning, though no client code has to change: on a reuse response data.hint was rewritten wholesale — instead of "deploy here" it now opens with REUSED — no new server was created and explains the risk; and the schema description of data.next was corrected, having previously named an empty galaxy slot as the only reason the field appears when it also arrives on a reuse response. No existing field or code changed and no client action is required. Do read reused before calling POST /v1/infra/servers/:id/deploy: a deploy replaces whatever is already running on that server.
NEW-0803-4: dictionary of catalog list-property values
A new entity catalog-product-property-enums exposes all possible options of a trade-catalog list property: GET /v1/catalog-product-property-enums, GET /v1/catalog-product-property-enums/:id, POST /v1/catalog-product-property-enums/search, and GET /v1/catalog-product-property-enums/fields. The entity is read-only: write operations and aggregation are not registered and answer 404, and data.batch comes back as an empty array.
Previously a product's list-property value was available only as an option identifier: the /v1/products family returns an object with a numeric value in PROPERTY_<N>, and GET /v1/products/fields describes the property by name alone — there was nothing to expand the identifier into text with. The option list is now requested directly: GET /v1/catalog-product-property-enums?filter[propertyId]=166&limit=1000 returns identifier-and-text pairs from which a String(id) → value map is built.
The filter[propertyId] filter is required: the dictionary is read one property at a time, and a request without it is rejected with 400 MISSING_REQUIRED_FILTER before Bitrix24 is called — on the list and search endpoints. The check looks at the presence of the key and does not extend to batch sub-calls. Only properties with propertyType: "L" have an enumeration — for a property of any other type the response is an empty list, not an error. Pagination is ordinary: limit + offset, with the end of the selection signalled by meta.hasMore. The ceiling is 5000 records per call. The catalog scope is required.
Existing pages were clarified in the same change. GET /v1/catalog-products/:id now shows a captured response carrying a list property and explains the value / valueEnum / valueId triple, plus the shape a listType: "C" property returns — a bare "Y"/"N" scalar, that is the checkbox state rather than an option id. GET /v1/products/:id explains that an empty property value means either an unfilled property or one served by the catalog family alone, and how to tell the two apart with a single cross-call. GET /v1/products/fields states outright that a PROPERTY_<N> descriptor carries the name only, and where to go for the values. The error reference now lists the real set of entities that require a filter.
FIX-0803-5: Support tickets work while the account is balance-frozen
Before
On a balance-frozen account every /v1/feedback route returned 402 ACCOUNT_FROZEN — you could not report a problem from within the product exactly when it mattered most.
After
The whole conversation stays reachable by key: POST /v1/feedback (create), GET /v1/feedback (list), GET /v1/feedback/{id} (open a ticket) and POST /v1/feedback/{id}/comments (reply to the team). All four only read and write your own data. Attachment uploads (POST /v1/feedback/attachments), attachment downloads and PATCH /v1/feedback/{id} remain behind the freeze gate, as does every other /v1 endpoint.
FIX-0803-6: include now returns the full related record, not just link metadata
Before
With ?include=<relation> the nested object carried only the link metadata — without the related record's id or fields.
After
include returns the full related record (id + fields), as the contract describes — a separate follow-up request for the related entity is no longer needed.
FIX-0803-7: rolling-out Open Channels dashboard methods return METHOD_NOT_YET_AVAILABLE
Before
On an account where the update had not yet arrived, the Open Channels dashboard methods returned a raw 422 BITRIX_ERROR — indistinguishable from a real integration error.
After
That response is recognised and returned as 422 METHOD_NOT_YET_AVAILABLE with the release version — a clear signal that the method is not yet available on this Bitrix24 account, not an integration failure. The answer stays the same under regular polling: such calls no longer count toward error-loop protection, so a clear 422 is not replaced by 429 ERROR_LOOP_DETECTED (for methods that are not rolling out, the protection works as before).
FIX-0803-8: source-storage writes return precise error codes instead of a generic 500
Before
Client-side source-storage write failures were masked behind a generic 500 SOURCE_STORAGE_ERROR, giving no actionable signal.
After
The cause is now distinguishable: insufficient balance returns 402 BILLING_INSUFFICIENT; a transient storage access-key issuance failure returns 503 STORAGE_STS_UNAVAILABLE (safe to retry). The source-storage error table lists both.
FIX-0803-9: a value list in a statuses filter is rejected with a clean 400, not a 500
Before
GET /v1/statuses with a value list in the filter ({field: {$in: [...]}} or an array) was forwarded to Bitrix24, and the dictionary method answered differently per field: on id and name an internal error that reached the client as 502 BITRIX_UNAVAILABLE, on entityId, statusId, semantics and sort a "value must be a string" error, and on categoryId a success response carrying another pipeline's records.
After
A value list is rejected before the Bitrix24 call with 400 UNSUPPORTED_FILTER on every filter field — the dictionary method supports it on none of them. A single exact value is accepted ({field: value}); request several values in separate calls or via POST /v1/batch.
FIX-0803-10: /v1/tasks/:taskId/time accepts a key with the task scope
Before
The task time-tracking endpoint returned 403 INSUFFICIENT_SCOPE to a key holding the task scope — only tasks worked, even though the two are aliases of one permission.
After
task and tasks are treated as aliases (as on every other task endpoint) — a key holding either one is accepted.
NEW-0803-11: the one-shot galaxy app create now accepts `healthPath`
The body of POST /v1/infra/servers with a source field now accepts the optional healthPath — the path used to check the app's readiness inside its container. Validation matches POST /v1/infra/servers/{id}/deploy: a string of up to 500 characters starting with /. The default is /.
Previously healthPath was declared only in the deploy body, so the one-shot call the platform itself recommends in GET /v1/me answered 400 UNKNOWN_PARAM — while that same description called healthPath honored on the galaxy path. The field is now accepted exactly where the recommendation promises it; on a standalone create it is ignored.
Impact on integrators
Nothing to change — the field is optional. If you previously had to split the call into two steps just for healthPath, one call is now enough.
FIX-0803-12: `/start` and `/wake` on a galaxy app now explain why they do not apply
Before
POST /v1/infra/servers/{id}/start and POST /v1/infra/servers/{id}/wake checked the status before the server kind, so a galaxy app outside the allowed statuses (say RUNNING, ERROR or STOPPED) got the standalone-server text: "Server is RUNNING; /start requires one of SLEEPING, ERROR, PROVISIONING". Technically true and useless: those statuses would not have helped either, and nothing said that a container app has no cloud VM at all.
After
The kind check now comes first, mirroring /reboot. For a galaxy app in such a status message names the reason (this is a galaxy app, it has no cloud VM) and userMessage names the operations that do work: POST /v1/infra/servers/{id}/deploy (valid in any of these statuses) and POST /v1/infra/servers/{id}/reboot (only for a running or errored app). The error code is unchanged — still SERVER_WRONG_STATE (422) with currentState and availableActions.
Impact on integrators
Nothing to change: the HTTP status and code are the same, only the text changed. The response for statuses INSIDE the allowed list is byte-identical — in particular /start and /wake on a sleeping galaxy app still answer the documented VM_MISSING.
FIX-0803-13: a galaxy app failure now names its actual cause in `provisionError`
Before
When an app built, started and then died, provisionError carried only the generic verdict: "the app did not stay running, check the logs" or, when the out-of-memory flag fired, "likely OOM at the galaxy memory limit". The real cause — say TypeError: webidl.util.markAsUncloneable is not a function from an incompatible runtime version — sat in buildLog, while the server list surfaces provisionError only. So the short text could not tell memory apart from code, and the "move to a standalone server" advice pointed the wrong way.
After
The same wording now gains a line from the container logs: … Actual cause from the container logs: <line>. The line is picked from the tail the platform captures on failure: first a typed exception or error code (TypeError: …, EADDRINUSE, FATAL ERROR: … heap out of memory), then the known build causes, then the last error-ish line. The tail goes through the same scrubbing as buildLog — internal host paths are replaced with <build-context>. When the tail holds nothing useful the text stays exactly as before. The memory wording is preserved and gains the cause: the oom flag sometimes fires where memory was not involved, and then the quoted line is the only truth the reader gets.
Impact on integrators
Nothing to change. The previous substrings are preserved, so a client matching on them keeps working; only the appended tail is new. The full log is still available in buildLog (GET /v1/infra/servers/{id}).
FIX-0803-14: `/reboot` on a sleeping galaxy app now kicks the host repair and says so
Before
A sleeping galaxy app whose host had lost its tunnel had no way back. The documented way to wake an app is a deploy, and a deploy against an unreachable host fails. The host tunnel repair was already kicked from the app reboot, but the kick sat BEHIND the status check that rejects a sleeping app — so it was never reached.
After
Before the same 422 SERVER_WRONG_STATE refusal the platform kicks a background host tunnel repair and, when a repair actually started, adds an optional hint object with reason, recovery (which call to retry) and retryAfterSeconds (a floor for the wait, not a promise). When no repair started — the kill switch is off, the tunnel is in fact alive, a repair is already running, or the machine is blocked from waking — hint is absent: claiming a repair that did not start would be a lie the client acts on. The same behaviour was added to the dashboard reboot.
Impact on integrators
Nothing to change: the code and HTTP status are the same and hint is additive. A client that reads hint only needs to retry POST /v1/infra/servers/{id}/deploy after the named delay.
FIX-0803-15: a zip source archive no longer fails at extraction on a galaxy app
Before
The platform detects the archive format from its leading bytes and advertises .zip as supported, but on the galaxy path (POST /v1/infra/servers with source, and POST /v1/infra/servers/{id}/deploy for a galaxy app) the archive was handed over for extraction without preparing the host. When the extractor was missing there, the deploy failed with text like exec: "unzip": executable file not found in $PATH — which said neither what to do, nor that the same archive as .tar.gz would have worked.
After
Before uploading a zip archive the platform installs the extractor on the host (the same step the standalone server path already ran). The step is idempotent: with the extractor already present it does nothing and costs no time on later deploys. If the install fails, the archive is not uploaded at all and the deploy ends with an honest reason plus the suggestion to re-send the same source as .tar.gz; the text is available in buildLog and in provisionError.
Impact on integrators
Nothing to change. Deploys with .tar.gz take the previous path unchanged.
NEW-0803-16: Source versions accept up to 500 MB, and a deploy from our own link now links to the version on any key
Before
An archive could be stored as a version only up to 200 MB, while the same archive was allowed inline in a deploy body up to 500 MB. A large project had exactly one way to ship — entirely inside the request body.
Separately: a {"source": {"url": "…"}} deploy using a link obtained from GET /v1/infra/servers/:id/sources/:versionId/download was not linked to the version when the server belongs to a personal vibe_api_* key. The response carried data.source.autoSaved: false with skippedReason: "external-url-or-toggles-off", and the version kept linkedDeployId and deployStatus empty.
After
The source-version cap is 500 MB on both intake endpoints: POST /v1/infra/servers/:id/sources and POST /v1/apps/:id/sources. The body is still read as a stream, so archive size does not affect intake speed. A Content-Length above the cap is rejected with 413 before the body is read. The value is published as capabilities.apps.sourceStorage.limits.maxBlobBytes in GET /v1/me — now 524288000.
A deploy from our own link is linked to the version regardless of the owner key type: the response carries data.source.autoSaved: true and savedVersionId, and the version gets linkedDeployId and deployStatus filled in.
Affected endpoints: POST /v1/infra/servers/:id/sources, POST /v1/apps/:id/sources, POST /v1/infra/servers/:id/deploy, GET /v1/me
FIX-0803-17: revoking access kills every key of the connection, not just the latest
Before
When a user went through consent again for the same application, the platform issued a new key but the previous one kept working. Revoking access killed only the key from the latest authorization — earlier keys still reached the API, while the user believed access was closed.
After
Revoking access kills every key the user issued to the application for that Bitrix24 account, including keys from earlier authorizations. A key that used to survive revocation now answers 401 KEY_INACTIVE. Keys issued by other employees of the same account are not affected. On the partner side the usual 401 KEY_INACTIVE handling is enough — prompt the user to authorize again.