
## Create an application

`POST /v1/apps`

Registers an OAuth application in your Bitrix24 account and creates a paired API key. Send a flat request body without a wrapper.

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|---------|
| `title` | string | yes | Application name, from 1 to 255 characters, not whitespace only |
| `scopes` | array | yes | Set of application scopes — access to Bitrix24 account data, at least one. To publish the application later, the set must include `placement`. The platform `vibe:*` scopes are listed only when the request comes from a key with a fixed rights set. In every other case the paired key receives them automatically — see "Known specifics". The list — [Scopes](/docs/scopes) |
| `description` | string | no | Application description, up to 2000 characters |
| `appUrl` | string | no | Application URL: an address on the platform or a public `https://` address outside it (no `localhost`, private network or credentials). An empty string is saved as `null` |
| `redirectUris` | array | no | OAuth redirect URLs. Defaults to the authorization completion URL and `http://localhost` |
| `mode` | string | no | Access mode of the paired key: `READONLY` or `READWRITE`. Defaults to the narrowest of the account policy mode, the calling key mode and the Cowork seat mode, if the calling key belongs to a seat, as a [project deploy key](/docs/cowork/deploy-key) does. A read-only key that omits this field therefore gets an application in its own mode. An explicitly requested mode with broader permissions than the seat mode is not rejected: the paired key is issued in the seat mode. The create response does not name the mode: read the actual mode from `data.accessMode` of [`GET /v1/me`](/docs/keys-auth/me) called with the paired key |
| `mobile` | boolean | no | The application supports the Bitrix24 mobile client — the account receives this flag at registration. Defaults to `false`. Can only be set at creation, see "Known specifics" |

`handlerUrl` is set by the platform — it is not accepted in the body. OAuth callbacks and opening a [placement](/docs/apps/placements) go through it. For the difference between `appUrl` and `handlerUrl`, see the [Applications](/docs/apps#appurl-and-handlerurl-are-different-addresses) section.

## Examples

The JavaScript examples run in Node.js on Linux/macOS. They save the one-time key to an `app.key` file in a separate private directory inside the home directory and print only the file path and application id. Move the key to your secret store.

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/v1/apps \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Sales dashboard",
    "scopes": ["crm", "user", "placement"],
    "appUrl": "https://app-abc12345.vibecode.bitrix24.com"
  }'
```

### curl — OAuth application

```bash
curl -X POST https://vibecode.bitrix24.com/v1/apps \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Sales dashboard",
    "scopes": ["crm", "user", "placement"],
    "appUrl": "https://app-abc12345.vibecode.bitrix24.com"
  }'
```

### JavaScript — personal key

```javascript
import { mkdtemp, writeFile } from 'node:fs/promises'
import { homedir } from 'node:os'
import { join } from 'node:path'

const keyDir = await mkdtemp(join(homedir(), '.vibecode-app-'))
const res = await fetch('https://vibecode.bitrix24.com/v1/apps', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'Sales dashboard',
    scopes: ['crm', 'user', 'placement'],
    appUrl: 'https://app-abc12345.vibecode.bitrix24.com',
  }),
})

const { data } = await res.json()
// The key is returned only once; only the owner can read the file
const keyPath = join(keyDir, 'app.key')
await writeFile(keyPath, data.rawKey, { mode: 0o600, flag: 'wx' })
console.log('App ID:', data.id)
console.log('Key file:', keyPath)
```

### JavaScript — OAuth application

```javascript
import { mkdtemp, writeFile } from 'node:fs/promises'
import { homedir } from 'node:os'
import { join } from 'node:path'

const keyDir = await mkdtemp(join(homedir(), '.vibecode-app-'))
const res = await fetch('https://vibecode.bitrix24.com/v1/apps', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'Sales dashboard',
    scopes: ['crm', 'user', 'placement'],
    appUrl: 'https://app-abc12345.vibecode.bitrix24.com',
  }),
})

const { data } = await res.json()
// The key is returned only once; only the owner can read the file
const keyPath = join(keyDir, 'app.key')
await writeFile(keyPath, data.rawKey, { mode: 0o600, flag: 'wx' })
console.log('App ID:', data.id)
console.log('Key file:', keyPath)
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | `true` on successful creation |
| `data` | object | The created application and its paired authorization key |
| `data.id` | string | Application identifier |
| `data.title` | string | Name |
| `data.description` | string \| null | Description |
| `data.scopes` | string[] | Set of application scopes — exactly what you passed in the body. The paired key's scopes depend on the calling key's permissions — see "Known specifics" |
| `data.handlerUrl` | string | Handler URL, set by the platform |
| `data.appUrl` | string \| null | Application URL |
| `data.redirectUris` | string[] | OAuth redirect URLs |
| `data.bitrixClientId` | string \| null | OAuth client identifier in the Bitrix24 account |
| `data.authorId` | string | Author identifier in the Vibecode platform. To identify the Bitrix24 employee, use `data.authorBitrixUserId` only when `data.authorBitrixUserIdSource: "member"` |
| `data.authorBitrixUserId` | integer \| null | Bitrix24 employee id in the same format as `id` in [GET /v1/users](/docs/entities/users/list). Treat it as the author's confirmed id only when the source is `member`. `null` when the id is unknown |
| `data.authorBitrixUserIdSource` | string \| null | Where the id came from: `member` — the author's confirmed membership of the account; `snapshot` — a value saved when the application was created: it is unconfirmed and may point to a different employee after ownership changes; `null` — no id |
| `data.portalId` | string | Identifier of the Bitrix24 account linked to the key. [GET /v1/me](/docs/keys-auth/me) returns the same field |
| `data.createdAt` | string | Creation date, ISO 8601 |
| `data.updatedAt` | string | Modification date, ISO 8601 |
| `data.placements` | string[] | Placement codes. The array is empty for a new application |
| `data.catalogStatus` | string | Catalog status. For a new application — always `PRIVATE` |
| `data.publishedAt` | string \| null | Publication date, ISO 8601. For a new application — `null` |
| `data.placementResizeEnabled` | boolean | Whether to adjust the placement iframe height to fit the application content. For a new application — `false`. Enable it through [Update an application](/docs/apps/update) |
| `data.mobile` | boolean | The "Supports BitrixMobile" flag the platform actually sent to the Bitrix24 account at registration. `false` when the field was not sent or the flag could not be applied, in which case the reason arrives in `warnings` |
| `data.hosting` | string | Where the app runs: `platform` — on a platform server linked to the app or owned by this Bitrix24 account, or on that server's verified own domain, or the app has no address at all, `platform-orphan` — an address on a platform subdomain with no live server behind it, `external` — any other address, including a platform server of another Bitrix24 account. Computed on every read |
| `data.externalHost` | string \| null | Host name from `appUrl` (punycode) for `external`, otherwise `null`. An `external` row can also carry `null` when the address has no host: the string does not parse as a URL or its scheme has no host |
| `data.externalRegistrable` | string \| null | Registrable domain of the host by the public suffix list, private suffixes included: `example.co.uk` for `shop.example.co.uk`, the whole address for an IP address. `null` whenever `externalHost` is `null` |
| `data.rawKey` | string | Ready-to-use authorization key `vibe_app_…` for the `X-Api-Key` header. Returned only at creation — neither [application data](/docs/apps/get) nor the [list](/docs/apps/list) shows it. Save it immediately. If the key is lost, recreate the application |
| `warnings` | string[] | Returned only when there is something to report. Two reasons: a title that lost its non-ASCII characters on the way in (see "Known specifics"), or a method of registering the application on the account that cannot pass the `mobile` flag. In the second case, the line starts with `mobile:`; turn the flag on in the application card on the Bitrix24 account. The application is still created and the response stays `201` |

## Response example

`rawKey` is shown with a truncated secret — this is not a working value.

```json
{
  "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",
    "authorId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "authorBitrixUserId": 42,
    "authorBitrixUserIdSource": "member",
    "portalId": "8b1f0e2a-3c4d-5e6f-7a8b-9c0d1e2f3a4b",
    "createdAt": "2026-06-24T09:12:45.781Z",
    "updatedAt": "2026-06-24T09:12:45.781Z",
    "placements": [],
    "catalogStatus": "PRIVATE",
    "publishedAt": null,
    "placementResizeEnabled": false,
    "mobile": false,
    "hosting": "platform",
    "externalHost": null,
    "externalRegistrable": null,
    "rawKey": "vibe_app_local_7c3d4e5f6a7b80_55556666_…_6666"
  }
}
```

## Error response example

400 — validation failed:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "title: Required"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `VALIDATION_ERROR` | Body validation failed: missing or whitespace-only `title`, empty `scopes`, invalid `appUrl` |
| 400 | `REST_REGISTRATION_FAILED` | Bitrix24 refused to register the application or its webhook and gave no reason. The response carries `error.details.incidentCode` — a six-character support reference that locates the log entry. Quote it when you contact support |
| 400 | `APP_URL_UNSAFE` | An address outside the platform must be a public `https://` address: no `http://`, no `localhost` or private network, no credentials in the URL |
| 402 | `INT_TARIFF_REQUIRED` | The route checks account access to the platform before anything else. The account is on a free Bitrix24 plan, so creation never starts. The response carries `userMessage`, the ways to resolve the refusal in `error.alternatives`, and the current and required plans with the upgrade link in `error.details`. The same code appears in the `403` row below, and these are different checks: `402` comes from the account access check that runs before any work starts, `403` from the refusal to issue the paired key |
| 402 | `INT_VIBE_PLUS_REQUIRED` | Account access is narrowed to a paid Vibe+ plan, and an ordinary commercial Bitrix24 plan is no longer enough. An account on a trial plan keeps its trial access and does not receive this code |
| 403 | `INFRA_DISABLED_FOR_PORTAL` | `scopes` explicitly asks for `vibe:infra` and the Bitrix24 account administrator has turned server management off. The application is not created. A request without that scope goes through on such an account as well: the scope the platform appends by default is dropped there silently |
| `403` | `KEY_STORAGE_SCOPE_REQUIRES_PERSONAL_KEY` | Storage can be granted only to a key of personal origin. Create a new personal key with storage access. |
| 403 | `INFRA_FORBIDDEN_FOR_COWORK_KEY` | The call was made with a Cowork key — such a key works with data only and cannot perform write operations. To issue a key that can, see [Project key for deploy](/docs/cowork/deploy-key) |
| 403 | `APP_CREATION_RESTRICTED` | The portal policy does not allow the caller to create applications: creation is disabled entirely, limited to Bitrix24 account administrators, or restricted to a list the caller is not on. A Bitrix24 account administrator grants the right. More — [Creation rights](/docs/access-rights) |
| 403 | `KEY_POLICY_READONLY_REQUIRED` | The portal policy allows only `READONLY`, but `READWRITE` was requested |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The request comes from a key in read-only mode, and the body explicitly asks for `mode: "READWRITE"`: the paired key never receives broader permissions than the calling key. Without the `mode` field such a key gets an application in its own mode. The Cowork seat mode does not produce this code: a request for broader permissions than the seat allows results in a key with the seat's access mode. More — [Access mode](/docs/keys-auth/access-mode) |
| 403 | `INT_TARIFF_REQUIRED` | The account gets access through its Bitrix24 plan, and issuing the paired authorization key requires a commercial plan. `error.details.upgradeUrl` is not returned. The same code with `402` above is a different check — it runs earlier and concerns account access rather than key issuance |
| 403 | `PORTAL_TARIFF_UNREADABLE` | The state of the account plan could not be confirmed right now. The cause may be temporary (the check queue is busy, a recent attempt, the time allowed for the check ran out) or in the key's rights. Repeat the check through `/v1/me?refresh=tariff` and follow `error.details.remedy`. `error.details.requiredTariffs` is empty and no purchase is offered. Contacting support is needed only if the repeated check keeps failing |
| 403 | `SCOPE_GRANT_REQUIRES_CONSENT` | The request comes from a key with a fixed rights set and declares a platform `vibe:*` scope that the calling key itself does not hold. The list of such scopes arrives in `error.details.unconsented`. The rights set is fixed for a [partner key](/docs/partner-connect), for a [Cowork](/docs/cowork) project key, for a personal key issued by the Vibecode dashboard form (it grants exactly the rights that were ticked) and for a key issued with `exactScopes: true` — [Management keys](/docs/management-keys). Scopes confirmed to the partner key on the consent page can be declared. How to identify such a key — "Known specifics" |
| 403 | `SCOPE_GRANT_REQUIRES_CONSENT` | The request comes from a [partner key](/docs/partner-connect) and declares `vibe:ai`: a partner key does not pass this scope on to the paired key, even when it holds the scope itself. `error.details.unconsented` carries `["vibe:ai"]` |
| 403 | `CONNECTOR_APP_INSTALL_FORBIDDEN` | A Bitrix24 account administrator has barred this employee from installing applications. It arrives where the application is installed by the connector module: on a self-hosted account, and on a cloud account once that issuance path is enabled for it. Retrying does not change the state; the right is granted by an account administrator. More — [Creation rights](/docs/access-rights) |
| 409 | `KEY_LIMIT_REACHED` | The key limit has been reached. `error.details` carries the quota state: `limit` — how many keys are allowed, `used` — how many are taken, `requested` — how many new keys the operation needs. Without `scope`, this is the per-user account limit; `scope: "portal"` means the platform-wide account cap, which a Bitrix24 account administrator cannot raise. `used` also counts application authorization keys and keys the platform issued itself, so the total can exceed what the key list in the dashboard shows — [more](/docs/keys-auth#how-many-keys-you-can-create) |
| 409 | `CONNECTOR_MODULE_NOT_INSTALLED` | The connector module is not installed on the Bitrix24 account — an application cannot be installed and a paired key cannot be issued through it. The state is permanent; retrying without installing the module will not help |
| 409 | `B24_USER_DELETED` | The Bitrix24 employee who owns the paired key is no longer active on the account. The state is permanent: neither a retry nor freeing a key slot helps — the employee has to be restored on the account, or the application created under another user |
| 422 | `APP_HOST_NOT_ALLOWED_HERE` | `appUrl` points at a named address of the platform app domain (`<name>.<domain>`, not `app-…`) whose name is not bound to a server of your Bitrix24 account. Nothing is stored and Bitrix24 is not called. A name of your own server is accepted and stored as that server's `app-…` address |
| 502 | `CONNECTOR_APP_INSTALL_FAILED` | The connector module failed to install the application for another reason. Neither the application nor the paired key is created, and the request can be retried |
| 502 | `CONNECTOR_REST_UNAVAILABLE` | Bitrix24 refused the issuance and there is no managed remedy: by the platform's data the account's plan or trial period is active. The original reason is in `error.details.reason`, and `error.details.retryable: true` says the state is transient. The response carries no upgrade offer: there is nothing to buy. The platform does not guarantee a fresh read of the account plan before answering. It arrives where the other connector codes do — when the application is installed by the connector module |
| 502 | `CONNECTOR_PLAN_REQUIRED` | Bitrix24 refused to issue the paired key: the account plan does not include Vibecode. This error is returned when no plan purchase can be offered: the account is self-hosted, is already on a paid plan, or belongs to an unrecognised region. The human-readable cause arrives in `error.userMessage`. The state is permanent: retrying will not help until the Bitrix24 plan changes. Where access is sold, the same refusal arrives as `402` with a plan paywall code from the table above |

The full list of general API errors — [Errors](/docs/errors).

## Known specifics

- **The platform reports non-ASCII characters lost from the title.** A value sent without explicit UTF-8 serialization arrives with question marks instead of letters: the bytes are lost on the client side, before the request is sent. Windows PowerShell sends values this way. The application is still created as is, and the response carries `warnings` naming the field — the title becomes the Bitrix24 catalog card name on publish, so fix it right away. A ready-to-run call with `UTF8.GetBytes` — [Windows / PowerShell and UTF-8](/docs/infra#windows-powershell-and-utf-8).
- **A pair is created: an application and an API key.** Registration creates an application record and a paired authorization key in one step. This affects deletion: the key frees a slot in the per-user limit only after [deleting the application](/docs/apps/delete).
- **Application scopes and paired-key scopes are different sets.** In `scopes` you declare access to Bitrix24 account data — `crm`, `user`, `placement`. If the calling key's rights set is not fixed, the paired key receives that set **plus** platform scopes: `vibe:infra` — servers and agents, `vibe:ai` — model calls, `vibe:search` — web search and research, `vibe:storage` — object storage (only when the calling key has PERSONAL origin). The authorization key can then create servers through [`POST /v1/infra/servers`](/docs/infra/servers/create) right away, even though `vibe:infra` was never in the create body. The `data.scopes` field of the response shows the application declaration, not the key's effective set — read the effective set from `data.scopes` of [`GET /v1/me`](/docs/keys-auth/me) called with that key.
- **A key with a fixed rights set grants the paired key exactly what was declared.** The rights set is fixed for a [partner key](/docs/partner-connect), for a [Cowork](/docs/cowork) project key, for a personal key issued by the Vibecode dashboard form and for a key issued with `exactScopes: true` — [Management keys](/docs/management-keys). An application created with such a key gets a paired key holding exactly what is listed in `scopes` — no platform scopes are added to it. If you need infrastructure, list `vibe:infra` in `scopes` at creation time, otherwise [`POST /v1/infra/servers`](/docs/infra/servers/create) answers `403 INFRA_SCOPE_REQUIRED`. The same goes for `vibe:ai`, `vibe:search` and `vibe:storage`. You can only declare a right that the calling key itself holds, otherwise the answer is `403 SCOPE_GRANT_REQUIRES_CONSENT`. Adding the right later through [updating the application](/docs/apps/update) does not work: `vibe:*` propagation never reaches a key with a fixed set.
- **Storage requires a personal key origin.** If the caller key was issued to an application or has unknown origin, `vibe:storage` is omitted from paired-key defaults. An explicit `vibe:storage` in `scopes` returns `403 KEY_STORAGE_SCOPE_REQUIRES_PERSONAL_KEY`, even if the caller already holds it. Create a new personal key with storage access.
- **You can identify a key with a fixed rights set before creating the application.** Call [`GET /v1/me`](/docs/keys-auth/me) with the key you plan to create the application with. The set is fixed when `data.scopes` matches what was ticked when the key was issued. Other keys carry `vibe:ai` and `vibe:search` in the set even when those were not ticked.
- **The application opens in the account interface.** Registration tells the Bitrix24 account that the application has an interface, so after creation it appears in the account menu, and with `mobile: true` in the mobile client as well. The mode is set at creation time and [updating the application](/docs/apps/update) does not change it. An application that never appeared in the menu is registered as API-only: turn the API-only mode off in the application card on the Bitrix24 account, or create the application again.
- **The `mobile` flag can only be set at creation.** `PATCH /v1/apps/:id` does not accept `mobile` — a sent value is ignored, see [Update an application](/docs/apps/update). For an existing application, turn the flag on in the application card on the Bitrix24 account, or create the application again.

## See also

- [Applications](/docs/apps)
- [Update an application](/docs/apps/update)
- [Delete an application](/docs/apps/delete)
- [Publish](/docs/apps/publish)
- [Scopes](/docs/scopes)
- [Keys and authorization](/docs/keys-auth)
- [Rights to create keys and apps](/docs/access-rights)
