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

API changes: August 18, 2026

← Changelog · August 2026

BC-0818-1: application and source-management contract clarified

Old format supported until: not provided

Before

OpenAPI did not describe the exact success responses, metadata limits, and some error codes for the application source-storage operations. One application's OAuth key could mutate another application and read or mutate the same author's server sources when the server belonged to a different key. The note limit counted UTF-16 code units, so some valid Unicode strings were rejected. An intermediary cache could retain a temporary download URL.

After

The eight-operation contract now describes the actual response schemas, limits, error codes, and access matrix. An OAuth application key can now directly manage only its own application and access sources of a server owned by that same key. When publishing its own application, an explicit sourceServerId may select the same author's server under a personal key, but not a server owned by another OAuth application. The author's personal keys and account-administrator keys retain access. V1 application-operation audit records store the key owner's Vibe UUID rather than the numeric Bitrix24 user ID. The note limit is counted in Unicode code points. Application and server temporary-link responses are marked Cache-Control: private, no-store, and the URL, including its storage path, is explicitly identified as a short-lived bearer credential.

No transition window is provided because an OAuth application key's former access to another key's resources was an access-control defect.

FIX-0818-2: `?refresh=tariff` on a self-hosted portal re-checks its access state at the source

Before

GET /v1/me?refresh=tariff on a self-hosted portal re-checked the plan but returned the portal's access state from the previous snapshot. A change made right before the call stayed invisible until a background check picked it up.

After

The request re-checks that state at the source as well, and the response is built from fresh data. A change made right before the call is visible immediately.

NEW-0818-3: workgroup roster with member roles

A new endpoint GET /v1/workgroups/:groupId/users returns the members of a workgroup together with their role in it. Previously the /v1/workgroups facade exposed only the owner and the member count, so an application that had to tell a head, a moderator and an ordinary member apart could not do so.

Every row carries userId and role. The role field holds the Bitrix24 letter verbatim: A owner, E moderator, K member. An unrecognised letter is passed through rather than mapped onto a known one or dropped, so branch on the values you know and treat anything else as no permission.

The operation takes no pagination parameters and returns the whole roster, so meta.total always equals the number of rows in data. An empty roster is an ordinary answer, and the owner is not necessarily among the rows — read the owner identifier from GET /v1/workgroups/:id. A 404 means the workgroup does not exist or is not visible to the Bitrix24 identity the key acts as, because Bitrix24 refuses both cases identically.

The endpoint returns membership data, not an authorization decision: what it shows depends on the identity behind the key, and Bitrix24 account administrator status is a separate fact served by GET /v1/users/me. Nested resources are not addressable through POST /v1/batch, so the operation is called on its own path only. Requires the sonet_group scope.

FIX-0818-4: deals: seven fields from GET /v1/deals/fields are now accepted in a filter

Before

GET /v1/deals/fields listed fields the filter would not accept. A POST /v1/deals/search carrying filter[leadId], filter[quoteId], filter[taxValue], filter[originId], filter[originatorId], filter[additionalInfo] or filter[lastActivityBy] was rejected with 400 UNKNOWN_FILTER_FIELD before Bitrix24 was called at all, even though Bitrix24 does accept those fields in a filter. A report selecting deals by their lead could not be built.

After

All seven fields are declared in the deals schema and are accepted in filter — on GET /v1/deals, on POST /v1/deals/search and on POST /v1/deals/aggregate. They were already accepted in select, but with an UNKNOWN_SELECT_FIELD warning; that warning is gone. Sorting by them already worked before this change and is unaffected. Field names in responses are unchanged. The set of groupBy axes is untouched.

Two notes on values. leadId, quoteId, originId, originatorId and additionalInfo are now marked nullable in GET /v1/deals/fields and in OpenAPI: they return null on a deal that was not created from a lead, from a quote or by import. They were absent from the specification before, so a client generated from it must be ready for null. And on the three string fields (originId, originatorId, additionalInfo) an empty string from Bitrix24 is normalised to null, exactly as it already is on every other string field of the API. Across a 300-deal sample no empty string occurred at all — Bitrix24 returns null on these fields — so no change on real data is expected; but if your code compares such a field with "", compare it against an empty value instead.

Six fields — utmSource, utmMedium, utmCampaign, utmContent, utmTerm and contacts — stay rejected in filter and sort, because Bitrix24 does not support them there. The rejection is deliberate: accepting them would produce a silently wrong selection. The reason is now visible up front, in the description of each field in GET /v1/deals/fields. To filter by contact, use contactId (the primary contact) or contactIds (any linked contact).

On leads, quotes and smart-process items, the five UTM fields remain available in responses but no longer pass the local filter or sort guard: Bitrix24 does not accept them in crm.item.list. Instead of a Bitrix24 422, the request now receives 400 UNKNOWN_FILTER_FIELD or 400 UNKNOWN_SORT_FIELD before Bitrix24 is called. Other operations on these fields are unchanged.

FIX-0818-5: empty workday method results are returned as null

Before

When Bitrix24 returned an empty successful result, the data field could contain a service envelope with result, total, and next fields instead of the result value.

After

POST /v1/workday/open, POST /v1/workday/close, POST /v1/workday/pause, GET /v1/workday/status, GET /v1/workday/settings, and GET /v1/workday/schedule return data: null when the successful result is empty.

Impact on integrations

Clients no longer need to extract an empty value from the Bitrix24 service response envelope.

NEW-0818-6: the field reference for orders and order statuses now returns names and explanations

Before

GET /v1/orders/fields and GET /v1/order-statuses/fields answered with nothing but a type and a read-only flag per field. There was no human-readable name and no explanation for any of the 54 fields, so a client had only the field name to go on. Generating a form or a typed model from such a schema was not possible: a name like recountFlag or empStatusId explains nothing on its own.

After

Every field of both entities now carries a label (short name) and a description (explanation) in the language of the segment. The explanation states what the type cannot: why price is accepted on create only (Bitrix24 recalculates the amount from the basket items), that requisiteLink arrives as an empty array when the link is unset, that clients, payments, basketItems and propertyValues are returned by the order card only, and that Bitrix24 requires the type field on every status update. The response grew; the set of fields and their types did not change, so existing requests keep working.

NEW-0818-7: machine issuance of promo codes: three V1 methods and two new integration-key scopes

Vibecode now exposes three V1 methods for an integration that hands out promo codes on its own: POST /v1/platform/coupons/issue issues a batch of codes inside an existing campaign, GET /v1/platform/coupons/campaigns lists the campaigns available for issuance, and GET /v1/platform/coupons/campaigns/{slug} reads one campaign by its code. Authorization is a platform integration key in the Authorization: Bearer header, with the coupons:issue and coupons:read scopes respectively; a platform administrator grants them when the key is issued.

The key issues codes but neither creates campaigns nor moves their ceiling: both stay with a platform administrator, otherwise the campaign ceiling would stop being a ceiling. A campaign is addressed by the same code (slug) that prefixes every promo code it issued, so the integrator and support share one identifier. The remainingToIssue field answers how many codes may still be issued; null means the campaign has no ceiling.

The Idempotency-Key header is required. Codes are returned once and are not stored on the platform side — only their hashes are — so there is nothing to replay: a request with an already used key gets 409 IDEMPOTENCY_KEY_ALREADY_USED with a reference to the issued batch, not a second set of codes. Issuing into a draft campaign is refused with 409 COUPON_CAMPAIGN_IN_DRAFT: codes handed out before the campaign is activated would be refused at redemption and cannot be reissued. The remaining refusals: 409 COUPON_CAMPAIGN_NOT_ISSUABLE — the campaign is finished or archived, 409 COUPON_CAMPAIGN_CAP_REACHED — the batch does not fit under the ceiling, 403 INSUFFICIENT_SCOPE — the key lacks the required scope, 404 CAMPAIGN_NOT_FOUND — no campaign carries this code.

Redeeming a promo code through a machine method is not part of this release: a code is redeemed by a person in the dashboard, under their own session and on their own account.

NEW-0818-8: promo codes for Cowork/Code tiers and the refusal codes of redemption

Vibecode now has promo codes. A partner gets a code from the organiser and redeems it in the Cowork/Code section of the dashboard: the granted tier switches on for their seat for a term the platform pays for, with no debit from the account balance. Campaigns, issuing a batch of codes and revoking unissued codes are run by the platform administrator; redemption is a user action in the dashboard and has no public API method.

A refused redemption answers with a code in the code field, and most causes collapse into a single COUPON_INVALID on purpose: "not found", "revoked", "already redeemed", "expired", "the campaign is over" and the campaign limits are indistinguishable from one another, so a refusal never confirms that a live code exists. Only the causes a person can act on stay distinguishable: COUPON_TOO_MANY_ATTEMPTS — too many attempts in a row; COUPON_PORTAL_ACCESS_GATED — the account has no Cowork/Code access yet; COUPON_SEAT_PAUSED, COUPON_SEAT_CANCELLED and COUPON_SEAT_CANCELLATION_SCHEDULED — the state of the seat prevents the grant; COUPON_TIER_DOWNGRADE_BLOCKED, COUPON_SEAT_ALREADY_ON_TIER, COUPON_SEAT_ALREADY_PAID_LONGER and COUPON_SEAT_IS_PAID — the current tier is already no lower than the granted one, or is paid further ahead. CONCURRENT_REDEMPTION stands apart: several codes of one campaign were redeemed at the same moment, and the attempt only needs repeating.

A promo code never downgrades the current tier and is never applied on top of an already paid seat — in both cases it stays with its holder and is redeemed later. A redeemed promo code cannot be revoked: revocation applies only to a code nobody has used yet.

NEW-0818-9: promo code in the Cowork/Code app: check a code and redeem it with a desktop key

The Cowork/Code app now accepts a promo code itself instead of sending the person to the dashboard. Two methods have appeared: POST /v1/cowork/coupon/preview shows what a code grants (tier, term, campaign name) without changing anything, and POST /v1/cowork/coupon/redeem redeems it and turns on the granted tier for a term the Vibecode platform pays for.

Both methods require a Cowork/Code desktop key with the vibe:cowork scope. An agent key carrying the same scope gets 403 COWORK_DESKTOP_KEY_REQUIRED: redemption is irreversible and one-shot, and there is no human behind such a key to make the decision.

The check answers 200 and reports valid: false with reason COUPON_INVALID when the code does not work. One reason covers every "code does not work" case, so the method never hints to someone guessing codes how a non-existent code differs from a revoked one. Valid here means "the code is live and the campaign is open": the seat's own conditions (already paid, already on this tier) are verified at redemption and may refuse after a successful check.

Redemption returns the granted tier, the term and an accessGranted field. false means the tier was granted but the Bitrix24 account administrator has not opened access to Cowork/Code yet — show that separately, otherwise the person sees success and runs into a closed door.

Affected endpoints: POST /v1/cowork/coupon/preview, POST /v1/cowork/coupon/redeem

NEW-0818-10: applications catalog in the API: list and card

The Vibecode API now exposes an applications family: GET /v1/applications returns the list and GET /v1/applications/:id a single card. Rows are scoped to the key OWNER rather than to the calling key, so applications created in the Vibecode dashboard are included too — their servers belong to other keys of the same person and never appear in GET /v1/infra/servers.

The scope parameter accepts mine, shared (applications of other people that you can access — both those shared with you personally and those open to the whole Bitrix24 account) and feed (the default), alongside the page and limit paging parameters. The card is also available to someone the application was shared with, not only to its owner; the viewer's relationship to the application arrives in the viewerState field.

The list response carries truncated next to total. In the feed scope the ordering is computed by the platform, so the selection has an upper bound: when truncated is true, total is that bound rather than the full number of applications, and there are no pages beyond it. Deriving a page count as total / limit is only valid while truncated is false; when it is true, reach for the mine and shared scopes, which have no such bound.

Every card carries two extra blocks. sources reports whether saved source versions exist and returns the latest one in the very form the version download accepts, so no upfront call for the version list is needed. activeOperation reports an operation in flight: its kind, step and start time; the value unknown means the operation did start but its outcome is not known. Both blocks are filled in only for whoever manages the application: a viewer the application was merely shared with receives an empty sources (hasVersions: false, both fields null) and activeOperation: null. The response shape does not change, so beware of the wrong conclusion: an empty sources on someone else's application means "this data is not disclosed to you", not "there are no versions".

One caveat about activeOperation: null means "no operation with a stored record", not "nothing is happening to this application". The field covers deployment, repair, server plan changes and moving a container between galaxies — other actions are not journalled by the platform and never surface here.

Where to open an application is no longer yours to work out: the card returns a ready openUrl and openTarget pair. openTarget currently has a single value — app, meaning the application's own address opens; both fields are null when there is nothing to open. The value set is closed and may grow, so treat an unfamiliar value as "nothing to open here" rather than as an error.

Important: for an application embedded into Bitrix24 both fields arrive null — the platform does not yet know the address such an application opens at inside the account. Its own address is deliberately NOT substituted into openUrl: that address leads to the gateway sign-in page rather than into the application, so following it would look successful without being so.

The isEmbedded field tells those two states apart — "embedded, opens inside Bitrix24" versus "not published yet". Both arrive with an empty open pair, yet the text a user should see differs. Do NOT infer embedding from the presence of a server: an embedded application with no server of its own is a normal state (the embedding is done, the code has not been deployed yet), and the flag does not depend on the server at all. The field is disclosed both to the owner and to someone the application was shared with.

The server summary gained a reachable flag — true when the server both runs and answers over the network. It is separate from status because those are different facts: a server can be up while the network tunnel to it is not, and by status alone such an application looks healthy. A client cannot check this from outside, so the platform computes the flag.

The flag already implies the RUNNING state: it never arrives true for another server state, so there is no need to conjoin it with status. For an application inside a galaxy (server.kind: "GALAXY_APP") the second half of the flag is taken from the galaxy HOST, not from the container itself — the host holds the connectivity, the container has no tunnel of its own by design. Hence a consequence worth knowing up front: a freshly created container that has not reached RUNNING yet (it only does so after its first source upload) arrives with reachable: false even on a fully healthy host. That means "the container is not up yet", not "the host is unreachable" — the flag alone cannot tell the two apart, status can.

The server summary gained a lastDeployedAt field — when the application was last deployed successfully. It is the only signal in this section that an application is actually lived in: updatedAt only moves when the card is edited, and sources.latestSavedAt means "code saved" and reaches only whoever manages the application. The stamp is written by the deployment itself at the moment it records success, so a failed deployment never moves it. Important: the field carries no history: for servers created before it existed it arrives null until their next deployment — we did not reconstruct the history, because the available source covers only one of the three deployment paths and a date would appear for some server kinds while missing for others.

Two more things worth knowing up front. The application id from this section and the id from GET /v1/apps are different values of different entities: an application card here, an application registration on the Bitrix24 account there. And the same thing by meaning arrives under different names: name here, title there. Do not carry one over into the other — the fields are not synchronized.

The list ordering is now stated explicitly so a page walk is reproducible. In the feed scope: pinned by you → your own → other people's, and within a group by updatedAt newest first, ties broken by id. In mine and shared: by createdAt newest first, ties broken by id. That secondary key is not a formality — applications created in a batch carry identical timestamps, and without it two pages of one walk could overlap or skip an application. Important: updatedAt only moves when the card itself is edited — a deployment does not touch it, so "freshness" in the feed means "when the card was last changed", not "when the application was last deployed".