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 FST_ERR_CTP_BODY_TOO_LARGE.
| Field | Type | Required | Description |
|---|---|---|---|
catalogTitle |
string | no | Title in the Bitrix24 account catalog. If not sent — the app's current value |
catalogDescription |
string | no | Catalog description |
catalogIcon |
string | no | Catalog icon code |
appUrl |
string | no | App address on Black Hole. If not sent — the app's current appUrl |
placements |
string[] | no | Placement codes. List of allowed codes — Available placements. If not sent — the app's current set |
sourceVersionId |
string | no | Specific source snapshot to publish, format v<number>. 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 17 fields returned by app data |
data.placements |
string[] | Bound placements |
data.catalogStatus |
string | After publication — PUBLISHED |
data.publishedAt |
string | null | Publication date, ISO 8601 |
warnings |
string[] | Present if some placements could not be bound. Lists the codes that did not bind |
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",
"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 |
|---|---|---|
| 404 | NOT_FOUND |
App not found or belongs to another portal |
| 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 | 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 fresh snapshot. The body carries a hint with steps for POST /v1/apps/:id/sources |
| 400 | NO_USER_TOKEN |
The app is not authorized on the portal via OAuth. Complete authorization and retry publication — Application user authorization |
| 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 |
Full list of common API errors — Errors.
Known specifics
- Publication requires the app to be authorized on the Bitrix24 account. Placement binding is performed by the app's OAuth token, so before the first publication the app is authorized on the Bitrix24 account at least once. 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 return. - 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 update also changes the placement labels. - Unpublished apps are 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.