
## Create an application from Cowork/Code

`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/Code 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 same endpoint also returns the scope presets and the access mode that the Bitrix24 account assigns to new keys.

## Parameters

| Parameter | Type | Req. | 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. It does not expire and remains valid for as long as the application exists. See [Idempotency](#idempotency) for details |

## Request fields (body)

| Field | Type | Req. | 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 | yes | Bitrix24 scopes for the key being issued. The array must not be empty; there is no default. 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 account's default mode is used; the same value is returned in the `mode` field of the creation parameters. If the account assigns read-only access to new keys, a request with `READWRITE` returns `403` — the account policy overrides the request |
| `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/Code key, so only two examples are provided. A request made with a key that lacks the `vibe:cowork` scope receives `403 INSUFFICIENT_SCOPE`.

### curl — Cowork/Code 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/Code 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 | 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 |
| `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. 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_ge5crx6rXOgoFikYi7G2inWA6dx7VO1V_9bcf92",
    "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": 1, "limit": 1, "requested": 1 }
  }
}
```

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`.

## 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 account, 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 was not found or has been revoked |
| 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 | `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/Code subscription for the user and portal |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The calling Cowork/Code 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` |
| 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: it belongs to an application that has since been deleted, OR the attempt reached Bitrix24 and failed there. In the second case, the key deliberately remains claimed because a retry could issue a second key in addition to the first. This state is permanent; use a new idempotency key |
| 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. It is lower than the platform-wide limit because that limit is divided across replicas |
| 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` | A write failed. TWO outcomes are possible, and the response cannot distinguish them: either the key was issued but the application card could not be written, or the claim itself failed and nothing was created. In the first case, an extra key appears in your Vibecode account; revoke it. In both cases, retry with a NEW idempotency key. Reusing the old one may return `409 IDEMPOTENCY_KEY_ALREADY_USED` |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 Network rejected the call or was unavailable while the key was being issued. Neither the application nor the key was created, but the idempotency key is SPENT: the error is returned after Bitrix24 has already been called, so the claim deliberately remains in place. Otherwise, a retry could issue a second key in addition to the first. Retry with a NEW idempotency key; reusing the old one deterministically returns `409 IDEMPOTENCY_KEY_ALREADY_USED`. 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. Make ONE retry with a new idempotency key. If the same error occurs again, stop and tell the user that the key owner must sign in again |
| 503 | `COWORK_FEATURE_DISABLED` | Cowork/Code is disabled at the platform level |
| 503 | `APP_CREATE_DISABLED` | Creating applications from Cowork/Code 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/Code 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.

## 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/Code 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 means the key has already been issued.** The quota slot is taken and there is no card. Retry with a new idempotency key and revoke the extra key — revoking frees the quota slot, deleting the key is not required.

**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/Code](/docs/cowork)
- [Errors](/docs/errors)
