For AI agents: markdown of this page — /docs-content-en/apps/publish.md documentation index — /llms.txt
Publish app
POST /v1/apps/:id/publish
Moves the app to PUBLISHED, binds placements to the Bitrix24 account, and makes the app visible to all employees.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id (path) |
string | yes | App identifier. List: GET /v1/apps |
Request fields (body)
The body is optional. With an empty body, values are taken from the app record — this is the primary scenario for re-publishing without changes. If you send a body, add the Content-Type: application/json header — without it, a request with a body returns 413 PAYLOAD_TOO_LARGE.
| Field | Type | Required | Description |
|---|---|---|---|
catalogTitle |
string | no | Title in the Bitrix24 account catalog. If omitted, the app's current value is used |
catalogDescription |
string | no | Catalog description |
catalogIcon |
string | no | Catalog icon code |
appUrl |
string | no | App address: a Black Hole server, a galaxy container, or an external https address. If omitted, the app's current appUrl is used |
placements |
string[] | no | Placement codes. List of allowed codes — Available placements. If omitted, the app's current set is used |
sourceVersionId |
string | no | Specific source snapshot to publish, format v<number>. Applies when source storage is enabled |
sourceServerId |
string | no | The server whose sources are published — the identifier from POST /v1/infra/servers/:id/deploy. Needed when the sources were stored by the deploy auto-save on a server that does not belong to this app's OAuth key: such a snapshot is kept with the server, not with the app. The X-Source-Server header means the same. The platform never picks a server for you — without this field the check looks for the app's own snapshot. Applies when source storage is enabled |
Examples
curl — personal key
curl -X POST "https://vibecode.bitrix24.com/v1/apps/YOUR_APP_ID/publish" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "placements": ["CRM_DEAL_DETAIL_TAB"] }'
curl — OAuth app
curl -X POST "https://vibecode.bitrix24.com/v1/apps/YOUR_APP_ID/publish" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "placements": ["CRM_DEAL_DETAIL_TAB"] }'
JavaScript — personal key
const res = await fetch(
'https://vibecode.bitrix24.com/v1/apps/YOUR_APP_ID/publish',
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({ placements: ['CRM_DEAL_DETAIL_TAB'] }),
}
)
const body = await res.json()
if (!body.success) throw new Error(body.error.code)
// body.warnings — array, present if some placements failed to bind
console.log(body.data.placements)
JavaScript — OAuth app
const res = await fetch(
'https://vibecode.bitrix24.com/v1/apps/YOUR_APP_ID/publish',
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({ placements: ['CRM_DEAL_DETAIL_TAB'] }),
}
)
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | true on successful publication |
data |
object | The app after publication — the same set of fields returned by Application data |
data.placements |
string[] | Bound placements |
data.catalogStatus |
string | After publication — PUBLISHED |
data.publishedAt |
string | null | Publication date, ISO 8601 |
warnings |
string[] | Advisory entries that do not cancel the publication. Some placements could not be bound — the entry lists those codes. The entry about removing extra placements differs depending on the rollout — see the note under the error table. The sourceServerId you passed went unused because source storage is disabled for the Bitrix24 account. The published server version was left without indefinite retention — that entry then carries a ready-made PATCH you can use to tag it. The catalog title or description lost non-ASCII characters in transit — that entry names the field. The set of entries is open: show an unfamiliar one as is rather than discarding it |
Response example
The data field mirrors the app object with a filled placements array:
{
"success": true,
"data": {
"id": "33c4d5e6-f7a8-49b0-1234-5c6d7e8f9012",
"title": "Sales dashboard",
"description": null,
"scopes": ["crm", "user", "placement"],
"handlerUrl": "https://vibecode.bitrix24.com/v1/bitrix-handler",
"appUrl": "https://app-abc12345.vibecode.bitrix24.com",
"redirectUris": [
"https://vibecode.bitrix24.com/oauth/complete",
"http://localhost"
],
"bitrixClientId": "local.7c3d4e5f6a7b80.55556666",
"prefix": "vibe_app_local_7c3",
"suffix": "6666",
"authorId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"authorBitrixUserId": 42,
"authorBitrixUserIdSource": "member",
"portalId": "8b1f0e2a-3c4d-5e6f-7a8b-9c0d1e2f3a4b",
"createdAt": "2026-06-24T09:12:45.781Z",
"updatedAt": "2026-06-24T10:03:18.204Z",
"placements": ["CRM_DEAL_DETAIL_TAB"],
"catalogStatus": "PUBLISHED",
"publishedAt": "2026-06-24T10:03:18.204Z"
}
}
Error response example
400 — the OAuth key lacks the placement scope:
{
"success": false,
"error": {
"code": "MISSING_SCOPE",
"message": "OAuth API key must have the placement scope to publish (catalog publishing binds placements on Bitrix24)."
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 403 | INFRA_FORBIDDEN_FOR_COWORK_KEY |
The call was made with a Cowork/Code key — such a key works with data only and cannot perform write operations. To issue a key that can, see Project key for deploy |
| 404 | NOT_FOUND |
App not found or belongs to another portal |
| 403 | FORBIDDEN |
The call was made with an authorization key vibe_app_… issued for a different application. Such a key publishes only its own application, even when both applications were created by the same author |
| 403 | FORBIDDEN |
Request is not from the app author and not from a Bitrix24 account administrator |
| 409 | ALREADY_PUBLISHED |
App is already in PUBLISHED status |
| 400 | VALIDATION_ERROR |
A body field failed validation — message lists the fields and the reasons |
| 400 | VALIDATION_ERROR |
The X-Source-Server header was sent more than once. Send it once with a single server ID |
| 400 | VALIDATION_ERROR |
The X-Source-Server value is not a server ID in UUID form |
| 400 | NO_OAUTH_KEY |
The app has no associated OAuth key |
| 400 | MISSING_SCOPE |
The OAuth key lacks the placement scope |
| 409 | SNAPSHOT_REQUIRED |
Source storage is enabled, but there is no saved snapshot. The snapshot's age does not affect the outcome. The body carries a hint: without sourceServerId it points at POST /v1/apps/:id/sources and explains how to publish sources from a server. With sourceServerId it points at POST /v1/infra/servers/:id/sources. The hint.reason field distinguishes two refusal causes — the app has no snapshot, or the named server has none. Two further values arrive only when the platform checks the snapshot's age. The hint's field set is described in Source storage |
| 404 | SERVER_NOT_FOUND |
sourceServerId names a server that does not exist, belongs to another portal, or has been deleted |
| 403 | NOT_AUTHORIZED |
sourceServerId names a server whose sources this request may not use. Rights over the app do not extend here — the request must come from the server-owner key, a personal key of the same user, or a Bitrix24 account administrator |
| 400 | NO_USER_TOKEN |
The app is not authorized on the portal via OAuth. Complete authorization and retry publication — Application user authorization. The body carries a hint with three fields: requiredAction — what to do, docsUrl — this page, oauthDocsUrl — the authorization sequence. This hint has its own field set, which differs from the SNAPSHOT_REQUIRED hint |
| 400 | TITLE_TOO_LONG_FOR_CATALOG |
The resolved title is longer than 100 characters. This happens when catalogTitle is not sent and the app's title exceeds the catalog limit |
| 502 | PLACEMENT_UNBIND_FAILED |
Bitrix24 did not confirm the removal of the extra placements. The app was NOT published and the catalog metadata from this request was not saved. The codes left on the account are listed in error.placements. The app's placements field has already been rewritten by then — re-read the app. Arrives only once the removal check is enabled — see the note below |
| 503 | NETWORK_DEVKEY_REQUIRED |
The Bitrix24 account is switched to developer-key transport and the app author holds no such key. A retry will not help — ask the author to reconnect the account. Arrives only once the removal check is enabled — see the note below |
Full list of common API errors — Errors.
The account-side check for removing extra placements is currently rolling out per account, so there are two cases:
- While the capability is not enabled on the account — publication answers
200even when the extra placements could not be removed on the account. The code that was not removed STAYS in the app'splacements, and the reason arrives as aFailed to unbind <code>entry inwarnings. That entry is the only sign the list has drifted from the account, so for now you have to parsewarnings. The codesPLACEMENT_UNBIND_FAILEDandNETWORK_DEVKEY_REQUIREDdo not occur on this endpoint. - Once enabled — an unconfirmed removal answers
502 PLACEMENT_UNBIND_FAILEDwith the list of codes inerror.placements, and the app is not published at all. A200carrying a placement that was not removed does not occur on this path — on a successful response the removal entries inwarningsare limited to confirmation notes: which transport confirmed the removal, and whether the second transport refused. Enabling it does not change the rest of the set — binding failures and the other reasons listed in thewarningsrow above arrive on this path too.
A 502 here does not mean "nothing changed". The app's placements field is written BEFORE the refusal is raised and already reflects the actual state on the account: the codes removed successfully are gone from it, and the unconfirmed ones remain. So re-read the app through Application data before retrying. A retry without the placements field publishes exactly the set the app holds now — including the placement you believed removed. To remove it, retry with an explicit placements that omits that code.
Known specifics
- Publication requires the app to be authorized on the Bitrix24 account. Placement binding is performed by the app's OAuth token, so the app must be authorized on the Bitrix24 account at least once before the first publication. Without it, publication returns
NO_USER_TOKEN. The authorization sequence — Application user authorization. Consent on the Bitrix24 account is enough to publish. You do not need to fetch the session token — the platform stores the app token on the return redirect. - Signing in on the app's own page does not create the app token. The "Sign in with Bitrix24" button at your app address grants access to the app — that is a different surface, and it has no effect on
NO_USER_TOKENat publication. The token appears only at the OAuth consent step on the Bitrix24 account side: the authorization linked above, installing the app on the account, or the first time an embedded placement is opened. If publication still returnsNO_USER_TOKENafter signing in on the app page, the account-side authorization never happened. - The token is long-lived and renews itself. Authorization is needed once, not before every publication: the platform refreshes the token with its refresh token. Re-authorization is required only if access was revoked on the account or the OAuth app was recreated.
- A deploy's
autoSaved: truedoes not always satisfy the publish check. The deploy auto-save stores the sources with the server you deployed to. If that server does not belong to the published app's OAuth key — for example, you deployed with a personalvibe_api_key — the snapshot is kept with the server, and the publish check, which looks at the app's own snapshots by default, does not see it. PasssourceServerIdfor that same server and publication will take the sources from there — saving them again throughPOST /v1/apps/:id/sourcesis unnecessary. The server identifier is the one in the deploy URL. The server's snapshots are listed byGET /v1/infra/servers/:serverId/sources. - The placement title is the app name. The left-menu item and the CRM tabs are labelled with the resolved
catalogTitle— there is no separate name for the menu. Renaming the app through updating the application also changes the placement labels. - Unpublished apps can be published again. Publication is accepted in
UNPUBLISHEDstatus too — the app returns toPUBLISHEDwith the same or new placements. Catalog metadata is preserved on unpublish, so with an empty body the catalog is restored without refilling it. - Some placements may not bind. Each code is bound separately. Codes that could not be bound do not interrupt publication — the app becomes
PUBLISHED, and the codes that did not bind are listed inwarnings.