
## Create an application

`POST /v1/apps`

Registers an OAuth application on the Bitrix24 account and creates a paired API key. The body is passed flat, without a wrapper.

## Request fields (body)

| Field | Type | Req. | Description |
|------|-----|:-----:|---------|
| `title` | string | yes | Application name, from 1 to 255 characters |
| `scopes` | array | yes | Set of application scopes — access to Bitrix24 account data, at least one. For future publishing the set needs `placement`. There is no need to list the platform `vibe:*` scopes, the platform grants them itself. List — [Scopes](/docs/scopes) |
| `description` | string | no | Application description, up to 2000 characters |
| `appUrl` | string | no | Application URL, only `http://` or `https://`. 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 Bitrix24 account policy |

`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. The difference between `appUrl` and `handlerUrl` — in the [Applications](/docs/apps#appurl-and-handlerurl-are-different-addresses) section.

## Examples

### 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
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()
// Save data.rawKey immediately — it is returned only once
console.log('App ID:', data.id)
```

### JavaScript — OAuth application

```javascript
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()
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `data.id` | string | Application identifier |
| `data.title` | string | Name |
| `data.description` | string \| null | Description |
| `data.scopes` | array | Set of application scopes — exactly what you passed in the body. The paired key's scopes are wider, see "Known specifics" |
| `data.handlerUrl` | string | Handler URL, set by the platform |
| `data.appUrl` | string \| null | Application URL |
| `data.redirectUris` | array | OAuth redirect URLs |
| `data.bitrixClientId` | string \| null | OAuth client identifier in the Bitrix24 account |
| `data.authorId` | string | Author identifier |
| `data.portalId` | string | Bitrix24 portal identifier |
| `data.createdAt` | string | Creation date, ISO 8601 |
| `data.updatedAt` | string | Modification date, ISO 8601 |
| `data.placements` | array | Placements, empty before publishing |
| `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.rawKey` | string | Ready-to-use authorization key `vibe_app_…`. The only `vibe_app_…` value in this response — use exactly this as `X-Api-Key`. Returned only in this response and never shown again |

## 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",
    "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,
    "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: `title` missing, empty `scopes`, invalid `appUrl` |
| 403 | `APP_CREATION_RESTRICTED` | Application creation on the portal is restricted to a list, and the author is not on it |
| 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 while the paired key is being created in `READWRITE` mode. Such a key can only issue an application with `mode: "READONLY"`. More — [Access mode](/docs/keys-auth/access-mode) |
| 409 | `KEY_LIMIT_REACHED` | The per-user key limit for the portal has been reached |
| 409 | `CONNECTOR_MODULE_NOT_INSTALLED` | The Vibecode Connector module is not installed on the portal — an authorization key cannot be issued. Retrying without installing the module will not help |

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

## Known specifics

- **The authorization key is returned only once.** The `rawKey` field is present in the creation response and nowhere else — neither in [application data](/docs/apps/get) nor in the [list](/docs/apps/list). If you did not save it at creation — recreate the application.
- **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`. The paired key receives that set **plus** four platform scopes: `vibe:infra` — servers and agents, `vibe:ai` — model calls, `vibe:search` — web search and research, `vibe:storage` — object storage. That is why a fresh authorization key creates 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.

## 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)
