For AI agents: markdown of this page — /docs-content-en/cowork/applications-key.md documentation index — /llms.txt
Issue or replace an application key
POST /v1/cowork/applications/:id/key
Issues a key to an application with an empty slot or replaces the one already issued, and returns the new key exactly once. This is how you publish an application added from the catalog: a raw key cannot be recovered, and re-issuing through a management key is not available to a desktop client.
Scope: vibe:cowork (Cowork/Code key) | Base URL: https://vibecode.bitrix24.com/v1 | Authorization: X-Api-Key
The issued field names the branch that ran: minted — the slot was empty and a key was issued, rotated — the key in the slot was replaced. You cannot know the branch in advance, so the platform decides. The endpoint serves two kinds of callers. The owner of the application gets the key in the application slot. A member of the development team of the application's server gets their own key for that server — the owner's key is neither changed nor shown to them.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
Parameterid (path) |
Typestring | Requiredyes | DescriptionApplication identifier. List: GET /v1/applications |
ParameterIdempotency-Key (header) |
Typestring | Requiredyes | DescriptionA string of 1 to 255 characters from the set [A-Za-z0-9_.:-]. It is scoped to the calling key. For how a retry is answered, see "Known specifics" |
Request fields (body)
| Field | Type | Required | Description |
|---|---|---|---|
FieldsyncServerEnv |
Typeboolean | Requiredno | DescriptionReplace the key in the environment of the application's server and restart the application. Defaults to false. It applies to the rotated branch only: on minted there is no previous key in the environment. The result is returned in the envSync response field |
The body is optional: a request without a body and a request with an empty {} object are equivalent. The schema is strict: an extra field returns 400 VALIDATION_ERROR. The body size is limited to 8 KiB.
Examples
The endpoint accepts only a Cowork/Code key, so only two examples are provided. Personal keys and OAuth application keys lack the vibe:cowork scope, so requests using them return 403 INSUFFICIENT_SCOPE.
curl — Cowork/Code key
curl -X POST https://vibecode.bitrix24.com/v1/cowork/applications/APPLICATION_ID/key \
-H "X-Api-Key: YOUR_COWORK_KEY" \
-H "Idempotency-Key: app-key-2026-09-22-01" \
-H "Content-Type: application/json" \
-d '{}'
JavaScript — Cowork/Code key
const res = await fetch('https://vibecode.bitrix24.com/v1/cowork/applications/APPLICATION_ID/key', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_COWORK_KEY',
'Idempotency-Key': 'app-key-2026-09-22-01',
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
})
if (!res.ok) {
const { error } = await res.json()
throw new Error(`${res.status} ${error.code}`)
}
const { data } = await res.json()
// The raw key arrives once — save it right away
if (data.rawApiKey === null) {
// This is a retry: the key has already been issued and is not shown again
} else {
saveKey(data.applicationId, data.rawApiKey)
}
// The previous key works until previousKey.graceUntil — deliver the new key to the server before then
if (data.previousKey?.graceUntil) {
console.log('The previous key stops working at:', data.previousKey.graceUntil)
}
Response fields
| Field | Type | Description |
|---|---|---|
Fieldsuccess |
Typeboolean | DescriptionAlways true on success |
Fielddata.application |
Typeobject | null | DescriptionApplication card in the same shape returned by GET /v1/applications/:id. Its key block already shows the new key. null in one case only: the key was issued and the card could not be read afterwards — warningCodes then contains APPLICATION_CARD_UNAVAILABLE |
Fielddata.applicationId |
Typestring | DescriptionRepeats application.id, so you can link your local record to the card without parsing the card itself |
Fielddata.issued |
Typestring | null | Descriptionminted — the slot was empty and a key was issued, rotated — the key in the slot was replaced. On a retry — what the original request did. null for a replacement made before the platform started recording the branch |
Fielddata.rawApiKey |
Typestring | null | DescriptionRaw key. It is returned ONCE and cannot be recovered. The format is vibe_api_, followed by 32 Latin letters and digits, _, and 6 hexadecimal characters. null on a retry |
Fielddata.key |
Typeobject | null | DescriptionThe new key without the secret — id, prefix, suffix, scopes, status, expiresAt, createdAt. null on a retry |
Fielddata.key.scopes |
Typearray | DescriptionScopes of the new key. An owner key issued into an empty slot carries only vibe:infra and vibe:storage. A replacement carries over the scopes of the previous key |
Fielddata.key.expiresAt |
Typestring | null | DescriptionExpiry of the new key, ISO 8601. null means the key has no expiry. On replacement the expiry is inherited from the previous key; see "Known specifics" |
Fielddata.previousKey |
Typeobject | null | DescriptionThe replaced key — id, graceUntil, status. Present only for the owner on the rotated branch, null in every other case |
Fielddata.previousKey.graceUntil |
Typestring | null | DescriptionWhen the previous key stops working: 24 hours after the replacement, or earlier if its own expiry comes first. null when the previous key was blocked — it gets no grace period |
Fielddata.envSync |
Typeobject | null | DescriptionOutcome of replacing the key in the server environment: the status is in the status field, the values are in the table below. null when syncServerEnv was not sent, on the minted branch, and in a response to a development team member |
Fielddata.warnings |
Typearray | DescriptionHuman-readable warning messages. Empty when there are none |
Fielddata.warningCodes |
Typearray | DescriptionWarning codes. APPLICATION_HAS_AUTH_KEY — the application also has an authorization key: the card keeps showing it, while this call changed the personal key. ENV_SYNC_NOT_APPLICABLE_FOR_COLLABORATOR — a response to a development team member. KEY_NOT_REPLAYABLE — a retry. APPLICATION_CARD_UNAVAILABLE — the card could not be read |
Values of envSync.status. The variable field names the environment variable that was touched, and the reason field refines skipped and recreate_failed.
| Status | What happened |
|---|---|
Statusupdated |
What happenedThe key was replaced in the application environment, and the application was restarted |
Statusrestart_failed |
What happenedThe key in the environment was replaced, but the restart failed — restart the application yourself |
Statusowner_restore_failed |
What happenedThe key in the environment was replaced, but the environment file was left unreadable to the application. A restart does not help until the application's access to the file is restored |
Statusnot_found |
What happenedThe previous key was not found in the environment, and the environment was not touched. Add the new key to the environment yourself |
Statusskipped |
What happenedNo replacement was attempted: reason: "no_server" — the application has no server, reason: "galaxy" — key delivery into containers is switched off for the account |
Statusunreachable |
What happenedThe server or container is asleep or unreachable, and the environment was not touched |
Statusfailed |
What happenedThe platform could not determine where to deliver the key, or the delivery broke off |
Statusrecreated |
What happenedThe container was recreated with the new key in its environment. While it restarts, the application drops its open connections — this is planned downtime |
Statusnot_required |
What happenedThe container environment carries no platform key, so there is nothing to change |
Statusbaked_in_image |
What happenedThe key is baked into the container image, and recreating the container does not update it. Override the variable at run time or rebuild the image from source |
Statusrecreate_failed |
What happenedThe replacement started and did not complete. The cause is in reason: rolled_back, app_down, busy, read_failed or timeout |
Statussuperseded |
What happenedA later key replacement has already been applied to this container |
Response example
Replacing the key in an occupied slot, issued: "rotated":
{
"success": true,
"data": {
"application": {
"id": "cmud90k3b0009cicg8yxl2rf8",
"name": "Deal report",
"description": null,
"type": "PERSONAL",
"iconUrl": null,
"createdAt": "2026-09-22T22:31:23.063Z",
"updatedAt": "2026-09-22T22:31:23.092Z",
"viewerState": "owner",
"pinned": false,
"author": { "name": "Application author" },
"isEmbedded": false,
"openUrl": null,
"openTarget": null,
"server": null,
"sources": { "hasVersions": false, "latestVersionId": null, "latestSavedAt": null },
"activeOperation": null,
"key": {
"slot": "api",
"present": true,
"prefix": "vibe_api_…",
"suffix": "9529",
"status": "ACTIVE",
"expiresAt": null,
"lastUsedAt": null,
"rotatable": true,
"rotateBlockedReason": null,
"affectsShownKey": true
}
},
"applicationId": "cmud90k3b0009cicg8yxl2rf8",
"issued": "rotated",
"rawApiKey": "vibe_api_…",
"key": {
"id": "b3d37d59-5b50-4e72-aa12-8a2b40a012f8",
"prefix": "vibe_api_…",
"suffix": "9529",
"scopes": ["crm", "vibe:infra", "vibe:storage"],
"status": "ACTIVE",
"expiresAt": null,
"createdAt": "2026-09-22T22:31:23.086Z"
},
"previousKey": {
"id": "5d192322-3ced-438e-bc4f-0cbba01b9937",
"graceUntil": "2026-09-23T22:31:23.087Z",
"status": "ACTIVE"
},
"envSync": null,
"warnings": [],
"warningCodes": []
}
}
Issuing a key into an empty slot, issued: "minted":
{
"success": true,
"data": {
"application": {
"id": "cmud90jwg0001cicg4qgipzws",
"name": "Sales dashboard",
"description": null,
"type": "PERSONAL",
"iconUrl": null,
"createdAt": "2026-09-22T22:31:22.816Z",
"updatedAt": "2026-09-22T22:31:22.889Z",
"viewerState": "owner",
"pinned": false,
"author": { "name": "Application author" },
"isEmbedded": false,
"openUrl": null,
"openTarget": null,
"server": null,
"sources": { "hasVersions": false, "latestVersionId": null, "latestSavedAt": null },
"activeOperation": null,
"key": {
"slot": "api",
"present": true,
"prefix": "vibe_api_…",
"suffix": "f08a",
"status": "ACTIVE",
"expiresAt": null,
"lastUsedAt": null,
"rotatable": true,
"rotateBlockedReason": null,
"affectsShownKey": true
}
},
"applicationId": "cmud90jwg0001cicg4qgipzws",
"issued": "minted",
"rawApiKey": "vibe_api_…",
"key": {
"id": "61a91657-2e88-4aeb-848f-1951327efd71",
"prefix": "vibe_api_…",
"suffix": "f08a",
"scopes": ["vibe:infra", "vibe:storage"],
"status": "ACTIVE",
"expiresAt": null,
"createdAt": "2026-09-22T22:31:22.879Z"
},
"previousKey": null,
"envSync": null,
"warnings": [],
"warningCodes": []
}
}
Error response example
403 — the key in the application slot is wider than the calling key:
{
"success": false,
"error": {
"code": "KEY_ROTATE_WIDER_THAN_CALLER",
"message": "The key in this application slot is wider than your key. Re-issue it from the web cabinet, where the owner sees what the new key will be allowed to do."
}
}
Errors
The key is minted through the same Bitrix24 channel as in application creation, and its refusals are passed through verbatim. On a self-hosted portal, the key is minted through a separate channel with its own codes, covered in Keys and authorization. The response shape is the same, so branch on error.code.
| HTTP | Code | Description |
|---|---|---|
| HTTP400 | CodeIDEMPOTENCY_KEY_REQUIRED |
DescriptionThe Idempotency-Key header was not sent |
| HTTP400 | CodeINVALID_IDEMPOTENCY_KEY |
DescriptionThe header was sent but does not match the allowed length or character set |
| HTTP400 | CodeVALIDATION_ERROR |
DescriptionAn extra field in the body, or a non-boolean syncServerEnv |
| HTTP400 | CodePORTAL_NOT_LINKED |
DescriptionThe Bitrix24 account is not linked to Bitrix24 Network, so the key cannot be minted |
| HTTP400 | CodeBOX_NO_DEVELOPER_KEY |
DescriptionSelf-hosted portal: the application owner has no developer key, so the key cannot be minted |
| HTTP401 | CodeMISSING_API_KEY |
DescriptionThe X-Api-Key header was not provided |
| HTTP401 | CodeINVALID_API_KEY |
DescriptionThe key is not recognized — no such key exists on the platform |
| HTTP401 | CodeKEY_INACTIVE |
DescriptionThe key is disabled |
| HTTP401 | CodeKEY_EXPIRED |
DescriptionThe calling key has expired |
| HTTP402 | CodeACCOUNT_FROZEN |
DescriptionThe account balance is exhausted — top it up |
| HTTP402 | CodeINT_TARIFF_REQUIRED |
DescriptionIssuance only, not replacement: the portal is on a free Bitrix24 plan |
| HTTP402 | CodeINT_VIBE_PLUS_REQUIRED |
DescriptionIssuance only, not replacement: account access is narrowed to a paid Vibe+ plan, so an ordinary commercial Bitrix24 plan is no longer enough. An account on a demo Bitrix24 plan keeps its trial access and does not receive this code |
| HTTP403 | CodeINSUFFICIENT_SCOPE |
DescriptionThe key lacks the vibe:cowork scope |
| HTTP403 | CodeCOWORK_HARNESS_KEY_FORBIDDEN |
DescriptionThe request used a third-party agent key issued for the subscription. The endpoint is not available to such a key — see Your own agent on the subscription |
| HTTP403 | CodeCOWORK_NOT_ACTIVATED |
DescriptionNo active Cowork/Code subscription for the user and portal |
| HTTP403 | CodeWRITE_BLOCKED_READONLY_KEY |
DescriptionThe calling key is in read-only mode, while issuing and replacing a key are writes. error.details names the key and its mode |
| HTTP403 | CodeNOT_APPLICATION_OWNER |
DescriptionThe caller neither owns the application nor belongs to the development team of its server |
| HTTP403 | CodeKEY_ROTATE_WIDER_THAN_CALLER |
DescriptionThe key in the application slot is wider than the calling key. Replacing it here would quietly narrow an application others may depend on, so such a key is re-issued in your Vibecode account |
| HTTP403 | CodeKEY_POLICY_READONLY_REQUIRED |
DescriptionDevelopment team member key only: the key being issued is wider than the mode the account assigns to new keys. The owner's empty-slot key is issued in the policy mode, so the owner never receives this refusal |
| HTTP403 | CodeINFRA_DISABLED_FOR_PORTAL |
DescriptionIssuance only, not replacement: the portal administrator has turned server management off, and the new key carries vibe:infra |
| HTTP403 | CodePORTAL_TARIFF_UNREADABLE |
DescriptionIssuance only, not replacement: the account plan could not be read, so the response does not name which plan is required. Buying a plan does not clear this refusal |
| HTTP404 | CodeAPPLICATION_NOT_FOUND |
DescriptionNo application with this id exists on the account, or it has been deleted |
| HTTP409 | CodeKEY_ROTATE_OAUTH_APP_KEY |
DescriptionThe personal slot holds an authorization key. Re-issuing it re-creates the OAuth application on the Bitrix24 account and changes its client_id, so it is done in your Vibecode account |
| HTTP409 | CodeKEY_ROTATE_SYSTEM_KEY |
DescriptionThe slot holds a key the platform issues through its own endpoint. There is no re-issue button for such a key in your Vibecode account — contact support |
| HTTP409 | CodeKEY_ROTATE_LIVE_OAUTH_GRANT |
DescriptionThe key carries a live authorization grant, and a replacement would leave it on the key being retired. Revoke the grant in your Vibecode account first |
| HTTP409 | CodeKEY_ROTATE_NOT_ACTIVE |
DescriptionThe key in the slot is revoked or expired. Put the key back in service or extend its expiry in your Vibecode account, then retry |
| HTTP409 | CodeKEY_ROTATE_KEY_VANISHED |
DescriptionThe key was deleted while the request was in flight. Retry |
| HTTP409 | CodeAPPLICATION_KEY_RECOVERY_REQUIRED |
DescriptionThe key in the slot is an unfinished replacement. A new idempotency key does not lift this block: recovery through support is required |
| HTTP409 | CodeAPPLICATION_KEY_REPLACE_IN_PROGRESS |
DescriptionAnother key replacement is already running on this application, or it completed while this request was waiting its turn. One replacement per application at a time, whatever idempotency key it carries. Wait for it to finish, read the card again, and only then retry. A replacement abandoned by a crashed request stops blocking after two minutes |
| HTTP409 | CodeKEY_LIMIT_REACHED |
DescriptionIssuance only, not replacement: the key quota is exhausted. error.details contains used, limit, and requested, and scope: "portal" means the platform's per-account key cap |
| HTTP409 | CodeCOLLABORATOR_KEY_REQUIRES_SERVER |
DescriptionDevelopment team member: the application has no live server, and a member key is bound to a server |
| HTTP409 | CodeCOLLABORATOR_MEMBERSHIP_BIND_CONFLICT |
DescriptionDevelopment team member: the membership could not be bound to the account safely, and no key was issued. Ask the server owner to add you again |
| HTTP409 | CodeIDEMPOTENCY_KEY_BODY_MISMATCH |
DescriptionThe same Idempotency-Key arrived with a different body |
| HTTP409 | CodeIDEMPOTENCY_KEY_ALREADY_USED |
DescriptionThe idempotency key is spent and there is nothing to replay: it was sent for a different application, the card was deleted, or a key was already issued under it and the request failed afterwards. Read the card and retry with a NEW idempotency key |
| HTTP409 | CodeIDEMPOTENCY_CONCURRENT_RETRY |
DescriptionA concurrent request with the same idempotency key is still running. This is the only one of the three idempotency refusals where a retry makes sense |
| HTTP413 | CodePAYLOAD_TOO_LARGE |
DescriptionThe body is larger than 8 KiB. It is checked before the body is parsed, so nothing is reserved and no key is minted |
| HTTP415 | CodeFST_ERR_CTP_INVALID_MEDIA_TYPE |
DescriptionThe body has a content type this route does not parse. Send Content-Type: application/json or a request without a body |
| HTTP429 | CodeRATE_LIMITED |
DescriptionThe rate limit for the account and key-owner pair has been exceeded. The platform-wide limit is 6 requests per minute. The effective limit for your key is returned in the x-ratelimit-limit header. |
| HTTP429 | CodeQUOTA_EXCEEDED |
DescriptionThe free daily call quota is exhausted while the prepaid balance is zero |
| HTTP500 | CodeAPPLICATION_KEY_ATTACH_FAILED |
DescriptionThe key was issued into an empty slot but could not be attached to the application. error.details.orphanKeyId names that key: revoke it and retry with a NEW idempotency key |
| HTTP500 | CodeAPPLICATION_KEY_REPOINT_FAILED |
DescriptionA new key was minted, but the card did not reach the verified final state. The key named in error.details.orphanKeyId stays under recovery protection, and a retry with another idempotency key is forbidden. If nothing points at that key, delete it the regular way, like any other key. A non-empty error.details.strandedSlots lists what still points at the key — in that case contact support |
| HTTP500 | CodeKEY_ROTATE_REVOKE_FAILED |
DescriptionReplacing a blocked key: revoking its live access tokens was not confirmed. The response carries no raw key, the minted key stays under recovery protection, and no retry is allowed until recovery completes |
| HTTP502 | CodeBITRIX_UNAVAILABLE |
DescriptionBitrix24 refused or was unreachable while the key was being minted. The previous key stays as it was: an active one keeps working, a blocked one does not |
| HTTP503 | CodeCOWORK_FEATURE_DISABLED |
DescriptionCowork/Code is disabled at the platform level |
See Errors for the full list of common API errors.
Known specifics
The issued key's mode is never wider than your key's. The Cowork/Code subscription mode does not affect the key the application owner receives. An empty slot gets a key in the narrower of the mode the account assigns to new keys and the calling key mode: a read-only policy does not refuse the issuance, it narrows it. A replacement gives a key no wider than the current mode of the replaced key and no wider than the caller.
The card tells you in advance whether the call goes through. The key block of the application card carries rotatable — whether this call goes through for you — and rotateBlockedReason with the refusal reason. WIDER_THAN_CALLER means the key in the slot is wider than yours, WRITE_BLOCKED_READONLY_KEY means your key is read-only. Read an unfamiliar reason as "cannot be replaced, reason unknown". The flag reflects what the platform already knows, so false is not a dead end and true is not a guarantee: handle the refusal from the endpoint itself anyway.
Without syncServerEnv the new key does not reach the server. The application keeps the previous key in its environment and starts getting refusals at previousKey.graceUntil — which is not always 24 hours away. Send true unless you have your own way to deliver the key to the server.
On replacement the new key inherits the expiry. A non-expiring key stays non-expiring, and an expiry that has not yet passed is carried over as is. An expiry that has already passed is replaced with the account's default lifetime — the same keyExpiresInDays that the creation parameters return.
A blocked key in the slot does not prevent a replacement. Its live access tokens are revoked rather than moved to the new key.
A development team member gets a key of their own. The key is bound to the application's server and opens only the development routes of that server. Such a call does not change the environment of the owner's server. If the calling key belongs to a Cowork/Code subscription, the member key uses the narrower of the calling key mode and the subscription mode: a read-only subscription produces a read-only member key, even when the calling key itself can change data.
A retry with the same idempotency key does not return the key. A retry with the same body returns 201 with the Idempotent-Replayed: true header. The secret is stored only as a hash, so save rawApiKey right away, in the same response handler.