## Bind a placement

`POST /v1/placements/bind`

Registers the application at the selected interface point of a Bitrix24 account — a card tab, a menu item, a chat panel. After the call, the placement appears in the interface under the title passed in the request.

Call preconditions — [what is required before binding](/docs/apps#placements).

On a cloud account, the platform first uses the transport selected for that account. If Bitrix24 rejects the developer key with a credential-only HTTP 401 and the app key is accompanied by a live `Authorization: Bearer` session, the bind is retried through OAuth automatically. A response that explicitly names a non-credential cause, such as an account-policy refusal or a missing scope, remains authoritative at every HTTP status and is never retried through OAuth. A successful response means one of these independent channels confirmed the write.

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `placement` | string | yes | Placement code. The list of codes available to the account — [Available placements](/docs/apps/placements/available) |
| `handler` | string | yes | Absolute URL of the application page that opens at this placement |
| `title` | string | yes | Placement title in the account interface, from 1 to 255 characters, not whitespace only |
| `options` | object | no | Placement settings. Besides the fields listed below, any other fields are accepted — for example `context`, `role`, `extranet` for chat widgets. The value of each field is a string, a number or a boolean. Nested objects and arrays are not accepted |
| `options.iconName` | string | no | Font Awesome icon name with the `fa-` prefix — for example `fa-comment`. Bitrix24 rejects a name longer than 50 characters. The list of names — [the Font Awesome 6 gallery](https://fontawesome.com/v6/search?m=free&s=solid). An icon is required for the `IM_SIDEBAR`, `IM_NAVIGATION`, `IM_TEXTAREA` placements, but the platform fills one in when the field is absent and reports it in the `optionsDefaulted` response field. Your own value always wins over the filled-in one. The `IM_CONTEXT_MENU` placement needs no icon |
| `options.errorHandlerUrl` | string | no | Error page URL for the `PAGE_BACKGROUND_WORKER` placement. If the field is not passed, the platform substitutes the current handler URL |
| `options.iconSvg` | string | no | Icon in SVG format, up to 10,000 characters |
| `options.width` | number | no | Width of the `IM_TEXTAREA` panel, in pixels. A positive integer |
| `options.height` | number | no | Height of the `IM_TEXTAREA` panel, in pixels. A positive integer |
| `options.color` | string | no | Icon color for the `IM_TEXTAREA` and `IM_SIDEBAR` placements, from the Bitrix24 chat palette: `RED`, `GREEN`, `MINT`, `LIGHT_BLUE`, `DARK_BLUE`, `PURPLE`, `AQUA`, `PINK`, `LIME`, `BROWN`, `AZURE`, `KHAKI`, `SAND`, `ORANGE`, `MARENGO`, `GRAY`, `GRAPHITE`. Not a hexadecimal code |

## Examples

### curl — OAuth application

```bash
curl -X POST https://vibecode.bitrix24.com/v1/placements/bind \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "placement": "CRM_DEAL_DETAIL_TAB",
    "handler": "https://example.com/deal-tab",
    "title": "Deal analytics"
  }'
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/placements/bind', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    placement: 'CRM_DEAL_DETAIL_TAB',
    handler: 'https://example.com/deal-tab',
    title: 'Deal analytics',
  }),
})

const { data } = await res.json()
console.log('Registered handler URL:', data.handler)
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` on a successful binding |
| `data.placement` | string | Code of the bound placement |
| `data.handler` | string | Handler URL registered on the account |
| `data.title` | string | Placement title in the account interface |
| `data.options` | object | Effective set of placement settings. Returned when the settings are passed in the request or substituted by the platform |
| `data.alreadyBound` | boolean | `true` when the placement was already listed as bound and the registration was performed again |
| `data.handlerRewritten` | boolean | `true` when the passed handler URL was replaced with the platform one |
| `data.requestedHandler` | string | Handler URL from the request. Returned together with `handlerRewritten` |

## Response example

The passed handler URL was replaced with the platform one:

```json
{
  "success": true,
  "data": {
    "placement": "CRM_DEAL_DETAIL_TAB",
    "handler": "https://vibecode.bitrix24.com/v1/bitrix-handler",
    "title": "Deal analytics",
    "requestedHandler": "https://app-a1b2c3d4.vibecode.bitrix24.com/deal-tab",
    "handlerRewritten": true
  }
}
```

Rebinding the same code:

```json
{
  "success": true,
  "data": {
    "placement": "CRM_DEAL_DETAIL_TAB",
    "handler": "https://example.com/deal-tab",
    "title": "Deal: summary",
    "alreadyBound": true
  }
}
```

Binding the `PAGE_BACKGROUND_WORKER` placement without `options` — the platform substituted the error page URL:

```json
{
  "success": true,
  "data": {
    "placement": "PAGE_BACKGROUND_WORKER",
    "handler": "https://example.com/worker",
    "title": "Background worker",
    "options": {
      "errorHandlerUrl": "https://example.com/worker"
    }
  }
}
```

## Error response example

400 — a required field is missing in the request body:

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

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `OAUTH_APP_REQUIRED` | The call was made with a personal key. Binding works only with an application authorization key |
| 400 | `VALIDATION_ERROR` | The request body failed validation — a required field is missing, `title` is whitespace only, or the placement code is not in the list of allowed ones |
| 400 | `PLATFORM_HANDLER_UNRESOLVABLE` | The passed URL points to the technical URL of the application server, and the platform handler could not be resolved. The placement is not registered |
| 400 | `APP_NOT_REGISTERED` | The application has no Bitrix24 application identifier |
| 400 | `BOX_NO_DEVELOPER_KEY` | The application author has no developer key configured |
| 401 | `SESSION_REQUIRED` | The `Authorization: Bearer` header with a session token is missing |
| 403 | `PLACEMENT_SCOPE_MISSING` | The key has no `placement` scope |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key works in read-only mode. In the Vibecode dashboard, open the page at the path from `error.details.switchUrl` and switch the mode to read and write there. Where the path leads for each kind of key — [Access mode](/docs/keys-auth/access-mode) |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key does not change Bitrix24 data, and the placement embeds into an employee work screen, or its handler after the Black Hole address rewrite is not the platform handler |
| 403 | `BOX_WEBHOOK_NOT_DEVELOPER_KEY` | The developer key used for the binding lacks the `rest.developer` scope. A key's scopes are fixed when it is issued — reconnect the account so the key is issued again. Returned before the account-access checks: a missing scope is not a question of the account's plan |
| 403 | `SESSION_REQUIRES_ADMIN` | The developer key belongs to a user without account administrator rights |
| 403 | `PLACEMENT_APP_GRANT_MISSING` | The placement is not available to the Bitrix24 application: its grant lacks the required scope. `details` carries the required scope in `requiredScope`, the placements available to the application (`availablePlacements`, `availablePlacementsTotal`) and the way to widen the grant in `remediation`. An application's scopes are fixed when it is installed — widening them requires re-issuing the authorization key or creating an application that requests the scope up front. Vibecode key scopes do not affect this |
| 400 | `PLACEMENT_OPTIONS_REQUIRED` | The placement has a required option that is absent from the request and that the platform could not fill in. `details` carries `missing` — the list of those fields |
| 400 | `PLACEMENT_NOT_REST_BINDABLE` | The placement cannot be bound over the API: Bitrix24 does not declare it an embedding point. Pick another code from [available placements](/docs/apps/placements/available) — placements like this one are marked there with `restBindable: false` |
| 403 | `B24_ACCESS_DENIED` | Bitrix24 rejected the developer-key credential with HTTP 401, and either no OAuth session was supplied or the OAuth retry also failed. This is a credential refusal, not a plan paywall. The safe `error.details` fields are documented below; the raw Bitrix24 response text and the developer-key URL are never returned |
| 403 | `INT_TARIFF_REQUIRED` | The account gets access through its Bitrix24 plan — a commercial plan is required. `error.details.upgradeUrl` is not returned. For accounts where access is narrowed to the Vibe+ plan line, infrastructure and key issuance additionally require a Vibe+ plan (`INT_VIBE_PLUS_REQUIRED`) |
| 404 | `APP_NOT_FOUND` | No application is linked to the key |
| 404 | `B24_EMBEDDING_APP_NOT_FOUND` | Bitrix24 does not know the application ID: the local application was deleted or reinstalled on the account. Create the local application again and call `POST /v1/apps/:id/relink-oauth` with the new `bitrixClientId` and `bitrixClientSecret`. Not to be confused with `APP_NOT_FOUND`, which is about the key-to-application link on the Vibecode side |
| 413 | `PAYLOAD_TOO_LARGE` | The body was not sent as JSON and is longer than one byte. Set the `Content-Type: application/json` header |
| 415 | `FST_ERR_CTP_INVALID_MEDIA_TYPE` | The body was not sent as JSON and is exactly one byte — the same cause, a different validation branch |
| 422 | `APP_HOST_NOT_ALLOWED_HERE` | `handler` or `options.errorHandlerUrl` 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 | `BITRIX_UNAVAILABLE` | Bitrix24 rejected the placement registration and the reason could not be determined. The Bitrix24 response code and status are returned in `details`, alongside `placementInAppList` when the placement is available to the application, and `diagnostics` — `"placement_list_skipped"` when the reason could not be checked with the account, or `"placement_list_empty"` when it answered with an empty list, which proves nothing |
| 503 | `NETWORK_DEVKEY_REQUIRED` | The developer key for the application author has not been issued yet |

### `B24_ACCESS_DENIED` fields

| Field | Type | Description |
|------|-----|----------|
| `error.details.bitrixCode` | string | Safe machine code from the rejected developer-key attempt |
| `error.details.bitrixStatus` | number | HTTP status returned by Bitrix24 |
| `error.details.oauthFallback` | string | `unavailable` when there was no OAuth session, or `failed` when the OAuth retry was also rejected |
| `error.details.oauthCode` | string | Optional safe machine code from the rejected OAuth retry |
| `error.details.oauthStatus` | number | Optional HTTP status from the rejected OAuth retry |

These fields are diagnostics, not a selector for another client-side transport attempt: the platform has already performed or declined the OAuth fallback. Clients branch on the outer `error.code`; the set of internal Bitrix24 codes is not a stable API enum.

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

The error table is segment-specific and documents the codes of this deployment. On the international surface, an account-access refusal is represented by `INT_TARIFF_REQUIRED`; do not copy codes from another deployment segment into a shared client enum.

## Known specifics

- **A placement can require a scope on the Bitrix24 application, not on the key.** Bitrix24 offers an embedding point only to applications whose grant covers the module that placement belongs to: task card tabs need the tasks scope, profile menu items need the users scope, and so on. The scope each code requires is returned per placement in [available placements](/docs/apps/placements/available) as `requiredScope`, and the summary of the gates in [key self-description](/docs/keys-auth/me) as `placements.bindPrerequisite`. An application's scopes are fixed when it is installed: they cannot be changed on a live application — re-issue the authorization key instead.
- **The platform fills in the options a placement requires.** A chat widget's icon and the background worker's error page URL are filled in when you omit them. The list of filled-in fields is returned as `optionsDefaulted`.
- **Rebinding re-registers the placement.** A call for an already bound code is not rejected: the platform removes the previous registration and binds the placement again, so the new handler URL and title take effect. This is the only way to change the title of an already bound placement.
- **`LEFT_MENU` adds a second menu item.** The application appears in the account menu as soon as it is [created](/docs/apps/create). Binding `LEFT_MENU` adds a second item with its own URL rather than replacing the first one. To remove the extra item, [unbind](/docs/apps/placements/unbind) the `LEFT_MENU` code — the item that appeared at creation stays in place.
- **A credential-only developer-key HTTP 401 does not mean a different plan is required.** It rejects that credential channel. With a live OAuth session the platform tries the independent channel; without one it returns `B24_ACCESS_DENIED` with the safe Bitrix24 machine code and status. A named account-policy or missing-scope refusal remains authoritative regardless of HTTP status and never enters the OAuth fallback.
- **The technical URL of the application server is replaced with the platform one.** If `handler` points to the technical URL of the application server, the platform application handler is registered on the account. External URLs are registered unchanged.
- **Bitrix24 augments `options` with its own values.** For chat widgets the account stores its own settings alongside the passed ones — display area, role and the extranet flag. They are returned in [bound placements](/docs/apps/placements/list) even when you did not pass them.
- **One title for all interface languages.** The `title` value goes to the account as a single label. This call does not set a separate translation. In [bound placements](/docs/apps/placements/list) the `langAll` field returns the same title for every language.
- **A successful response means the account accepted the write, not that the record stayed in place.** Check the actual account state at any moment after binding with the `portalSync` field in [bound placements](/docs/apps/placements/list): `ok` — the account returned the code, `drift` — the code is listed in Vibecode but the account does not have it.
- **One placement can be occupied by several applications.** Bitrix24 distinguishes registrations by application, so binding the same code from a second application on the same account is not rejected and does not remove the first registration. Sharing the same platform handler URL across applications does not change that.

## See also

- [Placements](/docs/apps/placements)
- [Available placements](/docs/apps/placements/available)
- [Bound placements](/docs/apps/placements/list)
- [Unbind a placement](/docs/apps/placements/unbind)
- [Key self-description](/docs/keys-auth/me)
- [Applications](/docs/apps)
