
## Опубликовать приложение в каталоге Битрикс24

`POST /v1/infra/servers/:id/b24-catalog/publish`

Создаёт карточку приложения в каталоге приложений Вайбкод на портале Битрикс24. До этого вызова карточка появлялась только после успешного [деплоя](/docs/infra/deploy/deploy), поэтому приложение, развёрнутое иначе, в каталоге отсутствовало, а выдача доступа сотрудникам его не показывала.

Правка [политики доступа](/docs/infra/access/access-policy) и списка доступа карточку не создаёт — она обновляет уже существующую. Публикация остаётся отдельным действием: карточка появляется в каталоге у владельца и у сотрудников, которым доступ уже выдан.

Это не публикация REST-приложения и не встройка в интерфейс — для них есть `POST /v1/apps/:id/publish` и [размещения](/docs/apps/placements).

## Параметры

| Параметр | В | Тип | Обяз. | По умолч. | Описание |
|----------|---|-----|:-----:|-----------|----------|
| `id` | path | string (UUID) | да | — | ID сервера, у которого ещё нет карточки в каталоге |

Тело запроса может быть пустым или `{}`. Неизвестные поля игнорируются.

## Примеры

### curl — личный ключ

```bash
curl -X POST -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" -d '{}' \
  https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/b24-catalog/publish
```

### curl — OAuth-приложение

```bash
curl -X POST -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" -d '{}' \
  https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/b24-catalog/publish
```

### JavaScript — личный ключ

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/b24-catalog/publish`,
  {
    method: 'POST',
    headers: { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
    body: '{}',
  }
)
const body = await res.json()
if (!body.success) {
  // CATALOG_ALREADY_PUBLISHED означает, что карточка уже есть — повторять не нужно
  console.error(body.error.code, body.error.message)
  throw new Error(body.error.code)
}
// Карточка появится в каталоге после ближайшего цикла синхронизации
console.log('Публикация поставлена в очередь')
```

### JavaScript — OAuth-приложение

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/b24-catalog/publish`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: '{}',
  }
)
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | `true`, когда публикация принята |
| `data.queued` | boolean | `true` — публикация поставлена в очередь. Карточку создаёт фоновая синхронизация, а не этот вызов |
| `data.op` | string | Операция в очереди. Для этого эндпоинта всегда `ADD` |

Состояние публикации читается через [`GET /v1/infra/servers/:id`](./get.md) в блоке `b24CatalogSync`:

| Поле | Тип | Описание |
|------|-----|----------|
| `b24CatalogSync.itemId` | number \| null | ID карточки в каталоге Битрикс24. `null` — карточки нет. Это признак «опубликовано», остальные поля описывают процесс |
| `b24CatalogSync.status` | string | `IDLE` — в очереди либо синхронизировать нечего, `PENDING` — обрабатывается, `SYNCED` — карточка синхронизирована, `FAILED` — синхронизация не удалась, `ORPHANED` — карточку удалили на стороне портала |
| `b24CatalogSync.pendingOp` | string \| null | Операция в очереди: `ADD`, `UPDATE`, `DELETE`. `null` — очередь пуста |
| `b24CatalogSync.attempts` | number | Число попыток синхронизации по текущей операции |
| `b24CatalogSync.eligible` | boolean | `false`, когда карточка невозможна: у сервера нет субдомена, на него ещё не выкладывали приложение, это рантайм агента или хост галактики, сервер переведён в режим `OPEN`, у него нет управляющего ключа или портала, либо синхронизация каталога отключена на стороне платформы. При `false` вызов публикации отвечает `400 CATALOG_NOT_ELIGIBLE` |

Значение `eligible: true` означает «карточка возможна», а не «публикация пройдёт»: у приложения с уже созданной карточкой вызов ответит `409 CATALOG_ALREADY_PUBLISHED`.

## Пример ответа

```json
{
  "success": true,
  "data": {
    "queued": true,
    "op": "ADD"
  }
}
```

## Пример ответа при ошибке

409 — карточка в каталоге уже есть:

```json
{
  "success": false,
  "error": {
    "code": "CATALOG_ALREADY_PUBLISHED",
    "message": "This server already has a Bitrix24 catalog card."
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 401 | `INVALID_API_KEY` | Неверный или просроченный API-ключ |
| 400 | `CATALOG_NOT_ELIGIBLE` | Карточка для этого сервера невозможна: нет субдомена, приложение ещё не выкладывали, это рантайм агента или хост галактики, сервер в режиме `OPEN`, у него нет управляющего ключа или портала, либо синхронизация каталога выключена на стороне платформы |
| 400 | `CATALOG_ORPHANED` | Карточку удалили на стороне портала. Восстановление доступно в личном кабинете, на этом эндпоинте — нет |
| 403 | `INFRA_FORBIDDEN_FOR_COWORK_KEY` | Вызов сделан ключом Cowork/Code — такой ключ работает только с данными, изменяющие операции ему закрыты. Что делать — [Проектный ключ для деплоя](/docs/cowork/deploy-key) |
| 409 | `CATALOG_ALREADY_PUBLISHED` | Карточка уже создана. Повторять вызов не нужно |
| 409 | `CATALOG_DELETE_PENDING` | По карточке уже поставлено удаление — публикация в этом состоянии недоступна |
| 404 | `NOT_FOUND` | Сервер удалён или управляется другим API-ключом |
| 429 | `RATE_LIMITED` | Превышен лимит запросов. В ответе приходит заголовок `Retry-After` с рекомендованной паузой |

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

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

- **Вызов ставит задачу в очередь, а не создаёт карточку.** Карточку создаёт фоновая синхронизация, поэтому `itemId` в [`GET /v1/infra/servers/:id`](./get.md) заполняется не в момент ответа. Отслеживайте готовность опросом: `b24CatalogSync.itemId` перестал быть `null` и `status` стал `SYNCED`.
- **Повторный вызов, пока публикация в очереди, безопасен.** Ответ снова `200`, вторая карточка не создаётся.
- **Управляющий ключ обязателен.** Публикация идёт от имени владельца управляющего ключа сервера, поэтому сервер, потерявший ключ, отвечает `400 CATALOG_NOT_ELIGIBLE`. Ключ возвращается в личном кабинете.
- **Выдача доступа карточку не создаёт.** Порядок такой: сначала публикация, затем [политика доступа](/docs/infra/access/access-policy) и список доступа. Обратный порядок тоже рабочий — после публикации карточка получит уже выданный доступ на первой же синхронизации.
- **Сначала выкладка, потом публикация.** Субдомен выдаётся при создании сервера, а приложение появляется на нём только после [выкладки](/docs/infra/deploy/deploy), поэтому на сервере без выкладки `eligible` приходит `false`, а вызов отвечает `400 CATALOG_NOT_ELIGIBLE`. Иначе карточка в каталоге вела бы на адрес, где никто не отвечает, а убрать её можно было бы только удалением сервера. Записи о выкладке платформа не имеет и тогда, когда приложение доставлено не через `deploy`, а загрузкой файлов, командой или по SSH — такой сервер тоже получает `400 CATALOG_NOT_ELIGIBLE`, хотя приложение на нём работает. Обойти это можно только настоящей выкладкой, а она по умолчанию (`cleanDeploy`) очищает каталог приложения, поэтому сначала сохраните то, что уже разложено.
- **Синхронизацию каталога можно выключить на стороне платформы.** В этом состоянии `eligible` приходит `false`, а вызов публикации отвечает `400 CATALOG_NOT_ELIGIBLE`: очередь никто не обработает, поэтому платформа отказывает сразу, а не оставляет карточку «в отправке» бессрочно.

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

- [Получить сервер](./get.md)
- [Политика доступа](/docs/infra/access/access-policy)
- [Список доступа](/docs/infra/access/access-list)
- [Развернуть приложение](/docs/infra/deploy/deploy)
