For AI agents: markdown of this page — /docs-content-en/source-storage/publish.md documentation index — /llms.txt
Snapshot before publishing
Publishing an application requires a saved source snapshot. This page covers what exactly is checked, what arrives in the 409 SNAPSHOT_REQUIRED refusal, and how to publish sources that the deploy auto-save stored on a server rather than on the application.
Storage overview and the endpoint reference — Source code storage.
Snapshot presence guarantee before publishing
POST /v1/apps/:id/publish checks that the app has a saved source snapshot. The snapshot's age does not affect the outcome — a version saved long ago is accepted just as readily as one created a moment ago. The 409 SNAPSHOT_REQUIRED refusal arrives when there is no usable snapshot: none was ever saved, or the files of the saved version have been deleted. The hint says which call to make before publishing again.
The check works only if source saving is enabled in the Bitrix24 account (enabled by default; the Bitrix24 account owner can disable it — Disabling for a Bitrix24 account).
Refusal example:
{
"success": false,
"error": {
"code": "SNAPSHOT_REQUIRED",
"message": "Publish requires a saved source snapshot for this app. Call POST /v1/apps/:id/sources, or publish with sourceServerId set to the server you deployed to — a deploy auto-save is stored on that server, not on the app.",
"hint": {
"requiredAction": "POST /v1/apps/:id/sources",
"reason": "app_snapshot_missing",
"serverKeyedSources": {
"requiredAction": "POST /v1/apps/:id/publish with sourceServerId (the server you deployed to)",
"bodyField": "sourceServerId",
"header": "x-source-server",
"listEndpoint": "GET /v1/infra/servers/:serverId/sources",
"registryEndpoint": "GET /v1/me/sources"
},
"toolName": "save_sources",
"lastSnapshot": null
}
}
}
The hint.lastSnapshot block is non-empty only when the platform checks the snapshot's age. Inside it, timestamp is when the version was created, presentedAt is when it was last presented by a save, and ageMinutes is counted from presentedAt: on a deduplicated save the two values diverge. The hint.freshnessWindowMinutes field arrives on the same condition — it is the length of the freshness window in minutes.
The hint.lastSnapshot field is null when there is no usable snapshot — none was saved, or the files of the saved version have been deleted. While the snapshot's age is not checked, that is the only cause of this refusal, so in that refusal the field is always null.
hint.reason names the refusal cause with one of two values: app_snapshot_missing, server_snapshot_missing. Two further values — app_snapshot_stale and server_snapshot_stale — arrive only when the platform checks the snapshot's age. The hint.serverKeyedSources block arrives only when the request named no server — it explains what to do when the sources were stored by a deploy auto-save.
Publishing sources from a server
The deploy auto-save stores sources on the server you deployed to. If that server does not belong to the OAuth key of the app you are publishing — for example, the deploy ran under a personal vibe_api_ key — the snapshot is kept on the server (its appId is empty), and the publish check, which looks at the app's snapshots by default, does not find it. The deploy answers autoSaved: true truthfully: the bytes are saved.
To publish exactly those sources, name the server:
curl -X POST "https://vibecode.bitrix24.com/v1/apps/$APP_ID/publish" \
-H "X-API-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sourceServerId": "'"$SERVER_ID"'"}'
The X-Source-Server: <serverId> header means the same.
What matters here:
The platform never picks the server. You pass the identifier — the same
:idyou called deploy with. Auto-picking "the nearest" server would publish sources nobody named.Rights to the server are checked separately from rights to the app. A server's sources can be published by the server-owner key, a personal key of the same user, or a Bitrix24 account administrator. No such server, a server from another Bitrix24 account, or a deleted one —
404 SERVER_NOT_FOUND; no rights —403 NOT_AUTHORIZED.A server has its own version numbering. Together with
sourceServerId, thesourceVersionIdfield means a version of THAT server:v3of the server andv3of the app are different versions.Publication does not mark a server's version. For the app's own version, publication adds the
publishedtag and writes a publication stamp; for a server's version it does neither. On a server version those same fields hold the DEPLOY history (which is whatGET /v1/infra/servers/:serverId/sourcesshows), and thepublishedtag means indefinite retention plus a delete block. Publishing one app changes neither of them on a version that does not belong to it. What was published is visible in the audit log as the publication record.A published server version has no indefinite retention. With no tag on it, it follows the ordinary cleanup rules: the platform always keeps the five most recent versions, plus one version per day for the last 14 days and one per week for the last 4 weeks. New deploys to the same server push the published version out of those rules, and it can be removed while the app stays published. A
warningsline in the publish response says the same. To keep the version forever, tag it yourself — that is your call, because indefinite retention takes up space and blocks deletion:curl -X PATCH "https://vibecode.bitrix24.com/v1/infra/servers/$SERVER_ID/sources/v3" \ -H "X-API-Key: $VIBE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"tags": ["manual"]}'Tags are replaced wholesale: if the version already carries labels of its own, list them in the same array next to
manual. The ready-made list arrives in that samewarningsline.Without
sourceServerIdeverything works as before — the check looks for a saved snapshot of the app, then tags it and writes the publication stamp.The header is validated like the field — but only when the field is absent. If
sourceServerIdcame in the body, the body decides: the header is not read, and whatever it holds has no effect on the answer. With no field in the body the header is validated strictly: a header that is not in UUID form, or is sent twice, gives400 VALIDATION_ERROR. It must not be silently ignored in that case — otherwise publication would take the app's snapshot while you believe you named a server.Source storage disabled for the Bitrix24 account — the selector is not used at all, publication proceeds, and
warningscarries a line saying thesourceServerIdyou passed went unused.
Where to find the server identifier if you did not keep it: GET /v1/infra/servers/:serverId/sources lists the versions of one server, and GET /v1/me/sources is the aggregated registry of every source the key can reach, by server and by app.
Parameters for re-publishing the same version (not the most recent one):
sourceVersionIdin the body — formatv<N>, for example"sourceVersionId": "v3".- The
x-source-version: v3header — an alternative to the body.
Error codes
POST /v1/apps/:id/publish refusals specific to the snapshot check:
| HTTP | Code | When returned |
|---|---|---|
| 409 | SNAPSHOT_REQUIRED |
There is no saved snapshot, or the files of the saved version have been deleted (only when saving is enabled). The response includes a hint field describing the action. |
The full code reference — Error codes.