
## Create an application from Cowork

`POST /v1/cowork/applications`

Creates a personal application and issues its key, which is returned exactly once. The endpoint does not create a server: a personal application has none by definition. Your deployment creates the server, which is then linked to the card automatically. The `Idempotency-Key` header is required.

**Scope:** `vibe:cowork` (Cowork key) | **Base URL:** `https://vibecode.bitrix24.com/v1` | **Authorization:** `X-Api-Key`

Take the request field values from the [application creation parameters](./applications-defaults.md). The creation parameters also include the scope presets and the access mode the issued key will get.

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|-------|----------|
| `Idempotency-Key` (header) | string | yes | A string of 1 to 255 characters from the set `[A-Za-z0-9_.:-]`. It is scoped to the calling key, so two different clients may send the same string. A key from a SUCCESSFUL attempt does not expire — it remains valid for as long as the application exists. A key from a FAILED attempt (the failure happened while the key was being issued, usually after Bitrix24 was already contacted) is replayed for 20 minutes and then forgotten. See [Idempotency](#idempotency) for details |

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|-------|----------|
| `name` | string | yes | Application name, 2 to 100 characters. Control characters are not allowed. Leading and trailing whitespace is removed before the length is measured, so a request with a one-character name returns `400` even if the name is surrounded by spaces |
| `type` | string | yes | Application type. The only supported value is `personal`. The schema accepts `external`, but the endpoint returns `400 APP_TYPE_NOT_AVAILABLE` |
| `b24Scopes` | array | no | Bitrix24 scopes for the key being issued. Omit it and the key gets every scope this account can grant, with `B24_SCOPES_DEFAULTED_TO_ALL` in `warningCodes`. A minted key cannot be narrowed afterwards, so send an explicit list when you do not need the full set. An empty array is refused: it would mean a key with no account scopes. Take the values from the `scopePresets` set in the [creation parameters](./applications-defaults.md) |
| `mode` | string | no | Key access mode: `READONLY` or `READWRITE`. If omitted, the key is issued in the narrowest of the account's default mode, the calling key's mode and, if the key belongs to a subscription, the Cowork subscription mode; that value is returned in the `mode` field of the creation parameters. An explicit value wider than the subscription mode is issued in the subscription mode, and the actual mode is returned in `data.mode` of the response. If the account assigns read-only access to new keys, a request with `READWRITE` returns `403` — the account policy overrides the request. While the write block for read-only keys is in force (it is by default), a read-only calling key gets no new key, with or without this field: creation returns `403 WRITE_BLOCKED_READONLY_KEY` before the remaining checks. Without the block it gets a read-only key, and an explicit `READWRITE` returns the same `403` |
| `expiresInDays` | number \| null | no | Key lifetime in days, as an integer of 1 or greater. If omitted or set to `null`, the key is issued with no expiry. The expiry policy from the creation parameters does not apply automatically; see "Known specifics" |

The schema is strict: an extra field in the body returns `400 VALIDATION_ERROR`, and the response names the field. The body size is limited to 64 KB.

## Examples

The endpoint accepts only a Cowork 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 key

```bash
curl -X POST https://vibecode.bitrix24.com/v1/cowork/applications \
  -H "X-Api-Key: YOUR_COWORK_KEY" \
  -H "Idempotency-Key: create-app-2026-08-25-01" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Deal report",
    "type": "personal",
    "b24Scopes": ["crm"]
  }'
```

### JavaScript — Cowork key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/cowork/applications', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_COWORK_KEY',
    'Idempotency-Key': 'create-app-2026-08-25-01',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Deal report',
    type: 'personal',
    b24Scopes: ['crm'],
  }),
})

if (!res.ok) {
  const { error } = await res.json()
  throw new Error(`${res.status} ${error.code}`)
}

const { data } = await res.json()

// The raw key is returned only once — save it immediately
if (data.rawApiKey === null) {
  // This is a retry: the application already exists, so the key is not issued again
} else {
  saveKey(data.rawApiKey, data.keyExpiresAt)
}
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data.application` | object \| null | Application card in the same shape returned by [`GET /v1/applications/:id`](/docs/applications/get). The fields populated immediately after creation are listed below. See the card page for the full list. `null` means the key has already been issued and attached to the application, but the card could not be read: the response is still `201` and contains the raw key (it is returned only once), and `warningCodes` includes `APPLICATION_CARD_UNAVAILABLE`. Read the card via `GET /v1/applications/:id` |
| `data.application.id` | string | Application identifier |
| `data.application.name` | string | The name you passed |
| `data.application.type` | string | `PERSONAL` for an application created by this endpoint |
| `data.application.server` | object \| null | `null` in the initial response because a personal application has no server yet. On a retry, the field reflects the state AT THE MOMENT OF THE RETRY, so it contains a server object if the application has since been deployed |
| `data.application.openUrl` | string \| null | `null` in the initial response because there is nothing to open yet. On a retry, it contains the current application URL if one has already been issued |
| `data.applicationId` | string | Repeats `application.id`. Returned separately so you can link your local record to the card without parsing the card itself |
| `data.rawApiKey` | string \| null | Raw application key. It is returned ONCE and cannot be recovered. The format is `vibe_api_`, followed by 32 Latin letters and digits, `_`, and 6 lowercase hexadecimal characters, for a total of 48 characters. `null` on a retry |
| `data.keyExpiresAt` | string \| null | Key expiry, ISO 8601. `null` means the key has no expiry. On a retry, the field is ABSENT rather than empty |
| `data.mode` | string | Access mode of the issued key — `READONLY` or `READWRITE`. On a retry the field is ABSENT |
| `data.warnings` | array | Human-readable warning messages. Empty when there are none |
| `data.warningCodes` | array | Warning codes. Contains `KEY_NOT_REPLAYABLE` on a retry, `B24_SCOPES_DEFAULTED_TO_ALL` when `b24Scopes` was omitted and the key got every scope the account can grant, and `APPLICATION_CARD_UNAVAILABLE` when the key was issued and attached but the card could not be read (`data.application` is then `null`). Both `warnings` and `warningCodes` are always present, even when empty, so the response shape is the same in every case |

## Response example

```json
{
  "success": true,
  "data": {
    "application": {
      "id": "cmt8ht5u40005ensk4fp45ebx",
      "name": "Deal report",
      "description": null,
      "type": "PERSONAL",
      "iconUrl": null,
      "createdAt": "2026-08-25T09:59:01.324Z",
      "updatedAt": "2026-08-25T09:59:01.324Z",
      "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
    },
    "applicationId": "cmt8ht5u40005ensk4fp45ebx",
    "rawApiKey": "vibe_api_…",
    "keyExpiresAt": null,
    "mode": "READWRITE",
    "warnings": [],
    "warningCodes": []
  }
}
```

## Error response example

409 — the key quota is exhausted:

```json
{
  "success": false,
  "error": {
    "code": "KEY_LIMIT_REACHED",
    "message": "Maximum number of API keys reached",
    "details": { "used": 40, "limit": 40, "requested": 1, "scope": "portal" }
  }
}
```

Deleting the issued key in your Vibecode account does NOT cancel idempotency: the application remains, and a retry with the same idempotency key still returns `201` with the `Idempotent-Replayed: true` header and `warningCodes: ["KEY_NOT_REPLAYABLE"]`. That retry does not issue a new key — the raw key is returned exactly once, during initial creation. To get a working key for an existing application, use `POST /v1/keys/:id/rotate`.

A failure that happened after Bitrix24 was contacted is replayed the same way — see [Retry after a failure](#retry-after-a-failure).

## Errors

The endpoint-specific error list is exhaustive: the endpoint does not return codes that are not listed here. Key issuance goes through Bitrix24 Network, whose errors are propagated verbatim and are also included in the table. For a self-hosted Bitrix24 portal, key issuance uses a separate channel and can return its own codes, covered in [Keys and authorization](/docs/keys-auth). The response shape is the same; branch on `error.code`.

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `IDEMPOTENCY_KEY_REQUIRED` | The `Idempotency-Key` header is missing |
| 400 | `INVALID_IDEMPOTENCY_KEY` | The header is present but does not meet the length or character-set requirements |
| 400 | `VALIDATION_ERROR` | The request body fails validation because of the length of `name`, control characters in `name`, an unknown `mode` value, `expiresInDays` below one, an empty `b24Scopes`, or an extra field. The message names the field |
| 400 | `APP_TYPE_NOT_AVAILABLE` | The request contains `type: "external"`, which is unavailable in this version |
| 400 | `INVALID_SCOPES` | `b24Scopes` contains a scope that is not in the Bitrix24 catalog. The message lists the rejected values |
| 400 | `PORTAL_NOT_LINKED` | The Bitrix24 account is not connected to Bitrix24 Network, so no channel is available for issuing the key |
| 400 | `PERSONAL_KEY_WEBHOOK_SCOPES_INVALID` | None of the scopes left in `b24Scopes` can be bound to the account. Returned when ONLY the `placement`, `entity`, or `userfieldtype` scopes are provided, because a personal key does not support them. Add at least one data-access scope, such as `crm` |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key is not recognized — no such key exists on the platform |
| 401 | `KEY_INACTIVE` | The key is disabled |
| 401 | `KEY_EXPIRED` | The calling key has expired |
| 402 | `ACCOUNT_FROZEN` | The account balance is exhausted — top it up |
| 402 | `INT_TARIFF_REQUIRED` | The portal is on a free Bitrix24 plan — creating an application requires a commercial plan, and a trial plan grants limited access. For accounts where access is restricted to Vibe+ plans, key issuance requires a Vibe+ plan, and an account on an ordinary commercial plan receives `INT_VIBE_PLUS_REQUIRED`. The response body includes the upgrade URL. Retrying without changing the account state returns the same response |
| 403 | `INSUFFICIENT_SCOPE` | The key lacks the `vibe:cowork` scope |
| 403 | `SCOPE_NOT_AVAILABLE_ON_PORTAL` | `b24Scopes` contains a scope for a module the account does not have. The message lists those scopes. The neighbouring key-issuing endpoints return the same error |
| 403 | `COWORK_HARNESS_KEY_FORBIDDEN` | The call was made with a third-party agent key issued for the subscription. Such a key cannot create applications — see [Your own agent on the subscription](/docs/cowork/harness) |
| 403 | `COWORK_NOT_ACTIVATED` | No active Cowork subscription for the user and portal |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The calling Cowork key was issued in read-only mode, but creating an application is a write operation. This error is returned before the remaining checks |
| 403 | `APP_CREATION_RESTRICTED` | The account policy restricts who can create applications, and the key owner is not allowed to do so. See [Rights to create keys and apps](/docs/access-rights) |
| 403 | `KEY_POLICY_READONLY_REQUIRED` | The account assigns read-only access to new keys, but the request body contains `mode: "READWRITE"`. The refusal is unconditional because this key has no administrative override |
| 409 | `KEY_LIMIT_REACHED` | The key quota is exhausted. `error.details` contains `used`, `limit`, and `requested`; without `scope`, this is the per-user account limit, while `scope: "portal"` means the platform's per-account key cap |
| 409 | `IDEMPOTENCY_KEY_BODY_MISMATCH` | The same `Idempotency-Key` was used with a different request body |
| 409 | `IDEMPOTENCY_KEY_ALREADY_USED` | The idempotency key has been consumed PERMANENTLY: it belongs to an application that has since been deleted. A failed attempt that reached Bitrix24 no longer returns this code — see [Retry after a failure](#retry-after-a-failure) |
| 409 | `IDEMPOTENCY_CONCURRENT_RETRY` | A concurrent request with the same idempotency key is still running. This is the only idempotency error for which a retry makes sense. The idempotency key is claimed BEFORE the application key is issued, so this error is returned before any key exists: no second application or key is created, no matter how many requests are sent concurrently |
| 415 | `FST_ERR_CTP_INVALID_MEDIA_TYPE` | The request uses a content type that this route does not parse. Send `Content-Type: application/json` |
| 429 | `RATE_LIMITED` | The rate limit for the combination of Bitrix24 account and key owner has been exceeded. The platform-wide limit is 6 requests per minute. The limit currently in force for your key is returned in the `x-ratelimit-limit` header. |
| 429 | `QUOTA_EXCEEDED` | The daily free-call quota is exhausted while the prepaid balance is zero |
| 413 | `PAYLOAD_TOO_LARGE` | The request body is larger than 64 KiB. For example, an unusually long `b24Scopes` can reach this size. The limit is checked before the body is parsed, so neither the claim nor the key is created |
| 500 | `APPLICATION_CREATE_FAILED` | An internal write or the key-issuing step failed, and the response cannot tell which. Three outcomes are possible: (a) the key was issued but the application card could not be written (an extra key appears in the API Keys section of your Vibecode account — revoke it); (b) the key-issuing step itself threw, for example on a database timeout — usually no key exists; (c) the reservation insert itself failed and nothing was created at all. In (a) and (b) the outcome is recorded: a retry with the SAME idempotency key within 20 minutes gets this same response (`Idempotent-Replayed: true`), so a replayed 500 does not prove an extra key exists. In (c) the reservation was never claimed, and a retry runs as a brand-new attempt right away, with no such header. See [Retry after a failure](#retry-after-a-failure) |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 Network rejected the call or was unavailable while the key was being issued — this is a failure AFTER Bitrix24 was already contacted, so the creation may have gone through on Bitrix24's side. A retry with the SAME idempotency key within 20 minutes gets this same response (`Idempotent-Replayed: true`) — see [Retry after a failure](#retry-after-a-failure). For a retry made PAST the 20-minute window, note that changing the idempotency key value does not protect against a duplicate — a retry with the old key and a retry with a new one both start a brand-new attempt. There is usually nothing on the platform to check either: the failed reservation is removed together with its outcome, and no key is minted for a failure at this stage — if the first attempt did reach Bitrix24, look for a duplicate on the Bitrix24 account itself. Treat the risk as inherent to retrying past the window and limit the number of retries. This code also covers a terminal case: the key owner's authorization in Bitrix24 Network has expired, and only the owner can resolve it by signing in to Bitrix24 Network again. The response does not distinguish the two cases and provides no separate field — instead of retrying to diagnose it, tell the user right away that the key owner must sign in to Bitrix24 Network again |
| 503 | `COWORK_FEATURE_DISABLED` | Cowork is disabled at the platform level |
| 503 | `APP_CREATE_DISABLED` | Creating applications from Cowork is disabled. The `available` field of the [creation parameters](./applications-defaults.md) reports this in advance |

See [Errors](/docs/errors) for the full list of common API errors.

## Idempotency

Unlike [creating a server](/docs/infra/servers/create), this endpoint requires the `Idempotency-Key` header. It issues a key and consumes a quota slot, so without idempotency, retrying after a network interruption could create a second application and a second key.

A retry with the same key and request body returns `201`, adds the `Idempotent-Replayed: true` response header, and returns the same card. The raw key is then `null`, and `KEY_NOT_REPLAYABLE` appears in `warningCodes`: the platform stores only the key hash, so the raw key cannot be recovered.

**A lost key is not returned on a retry, and this Cowork key cannot reissue it** — the response contains no identifier for the issued key, and the `/v1/keys` section accepts only a management key. The only practical option is to create the application again with a NEW `Idempotency-Key` value, obtain a fresh key, and then remove the extra card and its key from your Vibecode account. Save `rawApiKey` immediately in the same response handler.

The fingerprint is calculated from a canonicalized request body. The order of fields and the order of values in `b24Scopes` do not affect the comparison, so reordering either still counts as the same request. Leading and trailing whitespace in `name` also has no effect, and `expiresInDays: null` is equivalent to omitting the field. Using the same key with a different body returns `409 IDEMPOTENCY_KEY_BODY_MISMATCH` instead of a result for a different request.

Repeat the same request body without modifying it. Omitting an optional field and explicitly providing the value that the account would supply produce DIFFERENT fingerprints. For example, if the first request omitted `mode` and the retry includes it, the response is `409 IDEMPOTENCY_KEY_BODY_MISMATCH`. For the same reason, `["crm"]` and `["crm", "crm"]` differ: the scopes are sorted before comparison, but duplicates are not removed.

## Retry after a failure

If the first attempt failed AFTER Bitrix24 was already contacted (the connector was unreachable, the Bitrix24 account refused, an internal write failed), the platform stores that outcome and, for **20 minutes**, returns it verbatim on a retry with the same `Idempotency-Key`: the same HTTP status, the same `error.code`, the same body, plus the `Idempotent-Replayed: true` header. Aside from that header, the platform's response is indistinguishable from the first failure — use the header to tell a replay from the first attempt.

After 20 minutes the outcome is forgotten: the platform physically frees the idempotency key, and a retry with it runs as a brand-new attempt — every check runs again, including a chance to succeed. The retry body must still match the first attempt's body, or, as always, `409 IDEMPOTENCY_KEY_BODY_MISMATCH` is returned; the exception is when the outcome is already forgotten (20 minutes have passed) — then the body is not compared against the forgotten failure at all, and a retry with ANY body starts a new attempt.

If the first failure happened AFTER Bitrix24 was already contacted (for example `502 BITRIX_UNAVAILABLE`), the account may already have received a webhook from the first attempt — a retry past the 20-minute window does not check for that and can mint a SECOND key on top of the first. Changing the `Idempotency-Key` value does not protect against that here — the same key and a new one both run as a new attempt past the window. Usually there is nothing on the platform to check either: a failed reservation is removed together with its outcome, and at most failure stages no key is minted at all — so look for a possible duplicate on the Bitrix24 account itself, if you can, and treat the risk as part of the cost of retrying past the window. The exception is `500 APPLICATION_CREATE_FAILED`: there the key is sometimes ALREADY issued (see the error table above), and then you can find and revoke the extra key in the API Keys section of your Vibecode account.

A refusal by one of the checks that run BEFORE the idempotency key is claimed (body validation failed, the key quota is exhausted, the account is not linked to Bitrix24 Network, the platform's Bitrix24 plan check did not pass) is not covered by this guarantee: the key is not kept in that case, and a retry with it is indistinguishable from the first request as far as the platform is concerned. Most plan-related refusals arrive before the key is claimed and are not replayed, but a refusal that the Bitrix24 account returned while the key was being issued is stored and replayed for 20 minutes. The response body does not tell them apart, so rely on the `Idempotent-Replayed` header: absent means the answer is fresh.

## Known specifics

**The default key lifetime does not apply — the `keyExpiresInDays` field in the creation parameters is a hint, not a default.** If `expiresInDays` is omitted, the key is issued with no expiry, and the response contains `keyExpiresAt: null`. To limit the lifetime, pass the number of days explicitly. You can present the value from the creation parameters as a suggestion to the user.

**The `placement`, `entity`, and `userfieldtype` scopes are not included in the issued key.** In a mixed set, they are dropped without an error because a personal key does not support them. An error is returned only when `b24Scopes` contains no other scopes. Check the scopes on the issued key rather than relying on the request you sent.

**The issued key cannot call AI or web search.** It carries `vibe:infra`, `vibe:storage`, and the requested Bitrix24 scopes, while `vibe:ai` and `vibe:search` are absent from both the scope string and the runtime scopes. An application created this way cannot spend the account wallet on AI models or web search.

**The order of errors matters: first the calling key's permissions, then the product state, then the cost.** The checks run in this order: the key's existence, expiry, read-only mode, and account balance freeze; the `vibe:cowork` scope and key class, whether Cowork is enabled, whether the subscription is active, and whether the application creation wizard is enabled; idempotency and the request body schema; and finally the account's platform access, application creation policy, and key quota. When multiple conditions fail at once, the first error in this sequence is returned.

**A `500 APPLICATION_CREATE_FAILED` refusal does not show whether the key was issued.** If the key was already issued but the card could not be created, the quota slot is taken — find and revoke the extra key. If the key-issuing step itself threw, there is usually no key, but the outcome is still stored. In both cases a retry with the same idempotency key within 20 minutes returns the stored failure, and the `Idempotent-Replayed: true` header alone does not prove an extra key exists — check your key list. If the reservation insert itself failed, neither the key nor the card exists — the reservation was never claimed, there is no stored outcome, and a retry runs as a brand-new attempt right away.

**Some card fields are not populated by this endpoint — retrieve them from the application catalog.** The field set matches [`GET /v1/applications/:id`](/docs/applications/get), but `pinned` is always `false` here, and `sources` and `activeOperation` are always empty, even if the application has been deployed and the user has pinned it in their Vibecode account. Read those fields from the application catalog; use the creation response for the identifier, name, and key.

**The server is linked to the card automatically.** After creating the application, create the server by calling [`POST /v1/infra/servers`](/docs/infra/servers/create) with the parameters from the `server` block of the [creation parameters](./applications-defaults.md). No separate call is needed to link the server to the card.

## See also

- [Application creation parameters](./applications-defaults.md)
- [Create a server](/docs/infra/servers/create)
- [Application card](/docs/applications/get)
- [Keys and authorization](/docs/keys-auth)
- [Cowork](/docs/cowork)
- [Errors](/docs/errors)
