
# Приложения

OAuth-приложение Вайбкод — это запись в каталоге портала Битрикс24, через которую ваш код встраивается в интерфейс: карточки CRM, левое меню, чаты и другие места встраивания. Жизненный цикл приложения проходит четыре шага: создать → развернуть код → задать адрес → опубликовать. До публикации приложение видит только автор, его адрес `appUrl` пуст, а список мест встраивания `placements` пустой — это нормальное состояние, а не ошибка.

**Базовый URL:** `https://vibecode.bitrix24.tech/v1` | **Авторизация:** `X-Api-Key` | **Скоуп для публикации:** `placement`

[Какой ключ выбрать](#какой-ключ-выбрать) | [Состояния приложения](#состояния-приложения) | [Полный цикл](#полный-цикл) | [Замена и объединение приложений](#замена-и-объединение-приложений) | [Места встраивания](#места-встраивания) | [Известные особенности](#известные-особенности) | [Windows / PowerShell и UTF-8](#windows-powershell-и-utf-8) | [Коды ошибок](#коды-ошибок) | [Справочник эндпоинтов](#справочник-эндпоинтов)

## Какой ключ выбрать

Раздел работает с двумя типами ключей.

**Личный API-ключ `vibe_api_…`** привязан к одному пользователю и одному порталу Битрикс24 и несёт свой вебхук портала. Запросы идут от лица владельца ключа, заголовок один — `X-Api-Key: vibe_api_…`, токен сессии не нужен. Этим ключом ведут записи приложений: создают, читают, обновляют и удаляют. Создание — это регистрация настоящего локального OAuth-приложения на портале, и на одном личном ключе таких приложений может быть несколько.

**Ключ авторизации `vibe_app_…`** — это ключ самого OAuth-приложения. Чтобы он действовал от лица конкретного пользователя, рядом с `X-Api-Key: vibe_app_…` нужен заголовок `Authorization: Bearer <session_token>` — по нему берётся персональный OAuth-токен этого пользователя. Это модель для приложений, которые работают от лица разных пользователей того портала, где приложение зарегистрировано.

**Места встраивания и публикация.** Запустить публикацию можно любым из ключей, но саму привязку мест встраивания выполняет OAuth-токен приложения — поэтому приложение нужно один раз авторизовать на портале по OAuth, иначе публикация вернёт `NO_USER_TOKEN`. Приложению, которое открывается внутри портала Битрикс24 от лица пользователя с прозрачной авторизацией, подходит только модель OAuth-приложения `vibe_app_…` — личного ключа для этого недостаточно.

**Сколько приложений можно создать.** Число ключей на одного пользователя портала ограничено — по умолчанию 10. Приложения расходуют тот же счётчик, что и личные ключи, отдельного лимита на приложения нет. При достижении лимита создание возвращает `409 KEY_LIMIT_REACHED`, а поднимает лимит администратор аккаунта. Подробнее — [Сколько ключей можно создать](/docs/keys-auth#сколько-ключей-можно-создать).

Подробное описание типов ключей, форматов и получения `session_token` — [Ключи и авторизация](/docs/keys-auth).

---

## Состояния приложения

| Статус | Что значит | Видно в каталоге |
|--------|------------|------------------|
| `PRIVATE` | Состояние по умолчанию сразу после создания. Места встраивания не привязаны к порталу | Только автору |
| `PUBLISHED` | Приложение опубликовано, места встраивания привязаны к порталу | Всем сотрудникам портала |
| `UNPUBLISHED` | Снято с публикации, места встраивания отвязаны, но карточка в каталоге сохранена | Всем, неактивно |

Переходы: `PRIVATE → PUBLISHED → UNPUBLISHED → PUBLISHED` — последняя стрелка это повторная публикация. Снятие с публикации возвращает в `UNPUBLISHED`, а не в `PRIVATE`: метаданные каталога сохраняются для повторной публикации.

Статус каталога возвращается в поле `catalogStatus` — одно из `PRIVATE` / `PUBLISHED` / `UNPUBLISHED`. Дата публикации, если приложение публиковалось, — в `publishedAt`. Определяйте статус именно по `catalogStatus`, а не по массиву `placements`: пустой `placements` не отличает `PRIVATE` от `UNPUBLISHED`, а снятое с публикации приложение может сохранить ранее привязанные коды.

### Почему `appUrl: null` и пустые `placements` до публикации

Частый вопрос: приложение создано, код развёрнут, `GET /v1/me` показывает `capabilities.apps.publish.available: true`, но [данные приложения](/docs/apps/get) отдают `appUrl: null` и пустой массив `placements`, а в каталоге приложения нет. Так и должно быть, пока приложение в статусе `PRIVATE`:

- `appUrl` пуст, пока вы не зададите его через [обновление приложения](/docs/apps/update) или не передадите в теле публикации.
- `placements` пуст, потому что места встраивания привязываются к порталу только в момент публикации.
- `catalogStatus` равен `PRIVATE`, а `publishedAt` — `null`, пока приложение не опубликовано.
- В каталоге приложение появляется только после [публикации](/docs/apps/publish).

### `appUrl` и `handlerUrl` — разные адреса

`handlerUrl` платформа задаёт сама, и менять его нельзя — через него идут обратные вызовы OAuth и открытие места встраивания. `appUrl` — ваш адрес на Black Hole, куда платформа перенаправляет открытие места встраивания. Этот адрес задаёте вы.

---

## Полный цикл

### Шаг 1. Создать приложение

[Создание](/docs/apps/create) регистрирует OAuth-приложение на портале и возвращает ключ **один раз** — в поле `rawKey` ответа. Сохраните его сразу: повторно ключ не показывается. Для последующей публикации в наборе скоупов **обязателен** `placement`.

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/apps" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Дашборд продаж",
    "scopes": ["crm", "user", "placement"],
    "appUrl": "https://app-abc12345.vibecode.bitrix24.tech"
  }'
```

### Шаг 2. Развернуть код и задать адрес

Разверните приложение на Black Hole-сервере ([Deploy API](/docs/infra/deploy/deploy)) и убедитесь, что `appUrl` указывает на его адрес. Если адрес не задан при создании — задайте его через [обновление](/docs/apps/update):

```bash
curl -X PATCH "https://vibecode.bitrix24.tech/v1/apps/APP_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "appUrl": "https://app-abc12345.vibecode.bitrix24.tech" }'
```

### Шаг 3. Опубликовать

[Публикация](/docs/apps/publish) переводит приложение в `PUBLISHED`, привязывает места встраивания к порталу и делает приложение видимым всем сотрудникам. Перед публикацией приложение должно быть авторизовано на портале по OAuth, а в наборе скоупов ключа должен быть `placement`.

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/apps/APP_ID/publish" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "placements": ["CRM_DEAL_DETAIL_TAB"] }'
```

Обратный путь — снятие с публикации и удаление — в разделе [Замена и объединение приложений](#замена-и-объединение-приложений).

---

## Замена и объединение приложений

Приложение выводится из работы двумя операциями: [снятие с публикации](/docs/apps/unpublish) убирает его из точек интерфейса портала, [удаление](/docs/apps/delete) скрывает приложение из каталога и обработчика портала и отзывает его ключ авторизации. Отвязывать места встраивания по одному перед этим не нужно — их снимает снятие с публикации.

### Вывести приложение из работы

1. **Снять с публикации** — `POST /v1/apps/:id/unpublish`. Приложение переходит в `UNPUBLISHED`, места встраивания отвязываются от портала, карточка каталога сохраняется. Вызов принимает только приложение в статусе `PUBLISHED`, для остальных ответ — `404 NOT_FOUND`.
2. **Перепривязать серверы к другому ключу**, если на ключе приложения есть работающие серверы. Пока они есть, удаление отвечает `409 APP_HAS_ACTIVE_SERVERS` и перечисляет мешающие серверы в `details.servers`.
3. **Удалить приложение** — `DELETE /v1/apps/:id`, ответ `204 No Content`. Ключ авторизации приложения перестаёт действовать сразу: запрос с ним отвечает `401 KEY_INACTIVE`. Повторное удаление того же идентификатора отвечает `404 APP_NOT_FOUND`.

```bash
# 1. Снять с публикации — места встраивания отвяжутся от портала
curl -X POST "https://vibecode.bitrix24.tech/v1/apps/APP_ID/unpublish" \
  -H "X-Api-Key: YOUR_API_KEY"

# 2. Удалить приложение и отозвать его ключ авторизации
curl -X DELETE "https://vibecode.bitrix24.tech/v1/apps/APP_ID" \
  -H "X-Api-Key: YOUR_API_KEY"
```

Приложение может понадобиться снова — остановитесь на первом шаге: в каталог его вернёт повторная [публикация](/docs/apps/publish) с пустым телом.

Ручная [отвязка места встраивания](/docs/apps/placements/unbind) решает другую задачу — убрать одну точку интерфейса у приложения, которое остаётся в работе.

### Заменить одно приложение другим

Новое приложение — отдельная регистрация на портале со своим ключом авторизации и своим списком мест встраивания. Порядок, при котором сотрудники не остаются без возможности:

1. [Создать приложение](/docs/apps/create) с нужным набором скоупов, развернуть код, задать `appUrl`.
2. Авторизовать новое приложение на портале по OAuth — без этого публикация отвечает `400 NO_USER_TOKEN`.
3. [Опубликовать](/docs/apps/publish) новое приложение с теми же кодами мест встраивания.
4. Снять с публикации и удалить старое приложение по шагам выше.

### Объединить несколько приложений в одно

Набор скоупов задаётся при создании приложения. Добавить скоуп к существующему нельзя: [обновление приложения](/docs/apps/update) с расширенным набором отвечает `403 OAUTH_SCOPE_CHANGE_REQUIRES_REISSUE`. Сузить набор тот же вызов позволяет. Поэтому объединение — это новое приложение с объединённым набором скоупов, а не переделка одного из существующих.

Порядок для трёх приложений, которые сводятся в одно:

1. Собрать наборы скоупов и коды мест встраивания трёх приложений в один список. Текущие значения отдаёт [список приложений](/docs/apps/list).
2. [Создать](/docs/apps/create) новое приложение с объединённым набором скоупов, развернуть код, задать `appUrl`.
3. Авторизовать новое приложение на портале по OAuth и [опубликовать](/docs/apps/publish) сразу со всеми кодами мест встраивания.
4. Перевести интеграции на ключ нового приложения. Ключи трёх старых приложений перестают действовать в момент их удаления.
5. Снять с публикации и удалить три старых приложения.

На время перехода старые и новое приложения занимают слоты общего счётчика ключей. Если он исчерпан, создание отвечает `409 KEY_LIMIT_REACHED` — освободите слот, удалив одно из старых приложений.

---

## Места встраивания

Место встраивания — точка интерфейса Битрикс24, в которой открывается ваше приложение: вкладка в карточке сделки, пункт левого меню, кнопка на панели списка, панель в чате. Публикация привязывает места из поля `placements`, а отдельные вызовы [привязки и отвязки](/docs/apps/placements) позволяют управлять ими поштучно, менять подпись и адрес обработчика без повторной публикации.

Перед первой привязкой нужно выполнить несколько условий — невыполнение каждого даёт свой код ошибки.

| Условие | Что будет, если не выполнено |
|---------|------------------------------|
| Ключ авторизации приложения `vibe_app_…`. Личным ключом места не привязать | `400 OAUTH_APP_REQUIRED` |
| Скоуп `placement` у ключа — нужен для привязки и отвязки, для чтения списка нет | `403 PLACEMENT_SCOPE_MISSING` |
| Скоуп Битрикс24 под группу мест: `crm` для карточек и списков CRM, `im` для чата, `task` для задач, `contact_center` для Контакт-центра | `403 PLACEMENT_APP_GRANT_MISSING` — имя недостающего права приходит в `error.details.requiredScope`. Права выдаются приложению при установке и потом не меняются, поэтому добавить право нужно до переустановки или `relink-oauth` |
| Токен сессии рядом с ключом: часть аккаунтов выполняет привязку по одному ключу, часть требует ещё и заголовок `Authorization: Bearer` | `401 SESSION_REQUIRED` |
| Активная подписка BitrixGPT + Маркетплейс или коммерческий тариф — что именно, зависит от аккаунта. Точное условие заранее отдаёт блок `placements.bindPrerequisite` в [данных ключа](/docs/keys-auth/me). На `.com` инфраструктура и выпуск ключей дополнительно требуют тарифа Vibe+ (`INT_VIBE_PLUS_REQUIRED`) | `403 B24_MARKET_SUBSCRIPTION_REQUIRED`, `403 B24_MARKET_TRIAL_USED` или `403 INT_TARIFF_REQUIRED` |

Условия выше касаются привязки и отвязки. Список привязанных мест требует только ключа приложения, а справочник [Доступные места](/docs/apps/placements/available) — исключение, он открыт любому действующему ключу, и перечень кодов лучше брать живым вызовом — он пополняется, и фиксировать его в коде не нужно. Подробности по каждой операции — [Места встраивания](/docs/apps/placements).

---

## Известные особенности

- **Приложение привязано к одному порталу.** Запись приложения создаётся на портале, чей ключ передан при [создании](/docs/apps/create), и работает от лица пользователей этого же портала. Чтобы то же приложение работало на другом портале, зарегистрируйте его там отдельным вызовом [`POST /v1/apps`](/docs/apps/create) с ключом нужного портала. Тиражируемое приложение — это один код, развёрнутый как отдельная запись на каждом портале.
- **Набор скоупов фиксируется при создании.** Сменить его у существующего приложения нельзя — чтобы работать с другим набором скоупов, пересоздайте приложение. Без `placement` в наборе публикация вернёт `MISSING_SCOPE`, а запрос, которому нужен скоуп вне набора, — `BITRIX_ACCESS_DENIED`. Подробнее на странице [Скоупы](/docs/scopes).
- **Места встраивания со смарт-процессами.** Динамические коды вида `CRM_DYNAMIC_<entityTypeId>_DETAIL_TAB` принимаются наравне со статическими — идентификатор зависит от портала.
- **Точный список кодов мест встраивания.** `placements` принимает только коды из фиксированного набора — актуальный перечень отдают [Доступные места](/docs/apps/placements/available). Коды вне набора возвращают `400 VALIDATION_ERROR` при привязке. Если нужного кода нет в `/available` — он не поддерживается.
- **`appUrl` может содержать путь.** Допустим не только голый поддомен, но и адрес с путём, например `https://app-abc12345.vibecode.bitrix24.tech/hh-connector`. Это рабочий приём для нескольких приложений за одним поддоменом Black Hole: разведите их по путям через обратный прокси, а каждому приложению задайте свой `appUrl` с нужным путём.
- **Коннектору открытых линий нужен скоуп `imopenlines`.** Приложению, которое регистрирует коннектор внешнего мессенджера для открытых линий, добавьте в набор скоупов `imopenlines`. Полный перечень доступных скоупов — на странице [Скоупы](/docs/scopes).

---

## Windows / PowerShell и UTF-8

Кириллица в `title` приложения может превратиться в знаки вопроса (`?`), если запрос отправляется из Windows PowerShell без явной сериализации в UTF-8. Это не проблема отображения на стороне портала — кириллические байты теряются ещё до отправки HTTP-запроса, на стороне клиента.

**Причина.** По умолчанию PowerShell перекодирует строку из параметра `-Body` у `Invoke-WebRequest` и `Invoke-RestMethod` в системную кодировку `windows-1251`, и кириллица теряется ещё до сборки запроса. Заголовок `Content-Type: charset=utf-8` здесь не помогает — к моменту его применения исходные байты уже потеряны.

**Решение.** Передавайте тело запроса массивом байтов UTF-8.

```powershell
# 1. Кодировка вывода консоли — на кодирование тела запроса не влияет
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# 2. Собрать JSON и преобразовать его в массив UTF-8 байтов
$body = @{
  title  = 'Дашборд продаж'
  scopes = @('crm', 'user', 'placement')
} | ConvertTo-Json -Compress

$bytes = [System.Text.Encoding]::UTF8.GetBytes($body)

# 3. Передать в -Body массив байтов (не строку) и указать кодировку в Content-Type
Invoke-WebRequest `
  -Uri 'https://vibecode.bitrix24.tech/v1/apps' `
  -Method POST `
  -Headers @{
    'X-Api-Key'    = 'YOUR_API_KEY'
    'Content-Type' = 'application/json; charset=utf-8'
  } `
  -Body $bytes
```

**Распространённые ошибки:**

- Сохранять `.ps1` с UTF-8 BOM — старые версии PowerShell могут не разобрать сам скрипт.
- Передавать в `-Body` строку (`-Body $body`) вместо массива байтов (`-Body $bytes`) — строка повторно перекодируется через системную кодировку.
- Полагаться только на `Content-Type: application/json; charset=utf-8` без `UTF8.GetBytes` — этот заголовок не восстанавливает потерянные байты, а лишь объявляет серверу заявленную кодировку тела.

Та же сериализация нужна при [обновлении приложения](/docs/apps/update) — `title` меняется через `PATCH /v1/apps/:id` тем же телом в UTF-8.

**Node.js (`fetch`) и Python (`requests`)** кодируют тело в UTF-8 сами, дополнительных шагов не требуется. Проблема специфична для PowerShell.

---

## Коды ошибок

Каждая страница эндпоинта несёт свою таблицу ошибок. Ниже — общие коды, которые возвращает любой эндпоинт раздела.

| Код | HTTP | Описание |
|-----|------|----------|
| `MISSING_API_KEY` | 401 | Отсутствует заголовок `X-Api-Key` |
| `INVALID_API_KEY` | 401 | Неверный API-ключ |
| `RATE_LIMITED` | 429 | Превышен лимит запросов |
| `BITRIX_UNAVAILABLE` | 502 | Портал Битрикс24 недоступен |
| `INTERNAL_ERROR` | 500 | Внутренняя ошибка сервера |

Полный список общих ошибок API — [Ошибки](/docs/errors).

---

## Справочник эндпоинтов

| Метод | Путь | Описание |
|-------|------|----------|
| GET | [/v1/apps](/docs/apps/list) | Список приложений портала |
| POST | [/v1/apps](/docs/apps/create) | Создать приложение |
| GET | [/v1/apps/:id](/docs/apps/get) | Данные приложения |
| PATCH | [/v1/apps/:id](/docs/apps/update) | Обновить приложение |
| DELETE | [/v1/apps/:id](/docs/apps/delete) | Удалить приложение |
| POST | [/v1/apps/:id/publish](/docs/apps/publish) | Опубликовать в каталоге |
| POST | [/v1/apps/:id/unpublish](/docs/apps/unpublish) | Снять с публикации |
| POST | [/v1/apps/:id/relink-oauth](/docs/apps/relink-oauth) | Перепривязать приложение к OAuth-приложению портала |
| GET | [/v1/placements](/docs/apps/placements/list) | Места встраивания, привязанные приложением |
| GET | [/v1/placements/available](/docs/apps/placements/available) | Справочник доступных мест встраивания |
| POST | [/v1/placements/bind](/docs/apps/placements/bind) | Привязать место встраивания |
| POST | [/v1/placements/unbind](/docs/apps/placements/unbind) | Отвязать место встраивания |

Интерактивный переключатель методов с примерами и полями ответа — [Эндпоинты](/docs/apps/endpoints).

## Смотрите также

- [Места встраивания](/docs/apps/placements)
- [Эндпоинты](/docs/apps/endpoints)
- [Ключи и авторизация](/docs/keys-auth)
- [Скоупы](/docs/scopes)
- [Deploy API](/docs/infra/deploy/deploy)
- [Хранилище исходников](/docs/source-storage)
- [Подписки на события портала](/docs/infra/event-subscriptions)
