
# Partner Connect

Подключение внешних сервисов к Битрикс24: ваше приложение запрашивает у пользователя разрешение на доступ к его порталу и получает API-ключ Вайбкод для дальнейших вызовов. Поток построен по схеме Authorization Code — той же, что у OAuth-провайдеров Google или GitHub.

**Базовый URL:** `https://vibecode.bitrix24.tech/v1` | **Авторизация:** `client_id` + `client_secret` партнёра | **Скоупы ключа:** запрашиваются на странице согласия

## Содержание

- [Когда применять](#когда-применять)
- [Как это работает](#как-это-работает)
- [Справочник эндпоинтов](#справочник-эндпоинтов)
- [Полный сценарий](#полный-сценарий) — Express-обработчик от и до
- [`GET /v1/connect/authorize`](#get-v1-connect-authorize) — старт потока, редирект на согласие
- [`POST /v1/connect/token`](#post-v1-connect-token) — обмен кода на API-ключ
- [Публичный клиент: поток с PKCE](#публичный-клиент-поток-с-pkce) — приложение без сервера
- [Вход с устройства без браузера](#вход-с-устройства-без-браузера) — телевизор, приставка, терминал
- [Использование API-ключа](#использование-api-ключа)
- [Доступные скоупы](#доступные-скоупы)
- [Время жизни и отзыв ключа](#время-жизни-и-отзыв-ключа)
- [Безопасность](#безопасность)
- [Регистрация партнёра](#регистрация-партнёра)
- [Типы клиента](#типы-клиента) — публичный или конфиденциальный, с примерами
- [Смотрите также](#смотрите-также)

## Когда применять

Сценарий — внешний SaaS-продукт, который интегрируется с порталами Битрикс24 ваших клиентов: CRM-аналитика, синхронизация с 1С, чат-ассистент, конструктор лендингов. Кнопка «Подключить Битрикс24» на вашем сайте запускает поток: пользователь выбирает портал и подтверждает скоупы, ваш сервер получает постоянный API-ключ и работает с порталом от имени пользователя.

Если вы пишете приложение, которое ставится **внутри** портала Битрикс24 и работает только с ним, — используйте обычный API-ключ, см. [Ключи и авторизация](/docs/keys-auth).

## Как это работает

```
Ваше приложение → [1. Redirect] → Вайбкод Consent Page → [2. Согласие]
    ↑                                                          ↓
    └──── [4. API-ключ] ←── [3. Code → redirect_uri] ──────────┘
```

1. **Redirect** — отправляете пользователя на `/v1/connect/authorize` с `client_id`, `redirect_uri`, `state` и списком скоупов.
2. **Согласие** — пользователь видит страницу Вайбкод, выбирает портал и подтверждает или отклоняет запрошенные скоупы.
3. **Код** — после одобрения пользователь возвращается на `redirect_uri` с параметрами `code` и `state`. При отказе — `redirect_uri?error=access_denied&state=...`.
4. **Обмен** — сервер партнёра отправляет `POST /v1/connect/token` и получает API-ключ.

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

| Метод | Путь | Описание |
|-------|------|----------|
| GET | [`/v1/connect/authorize`](#get-v1-connect-authorize) | Старт потока: редирект на страницу согласия |
| POST | [`/v1/connect/token`](#post-v1-connect-token) | Обмен одноразового кода на постоянный API-ключ |

## Полный сценарий

Пример Express-обработчика, который проводит пользователя через оба эндпоинта от и до. Перед запуском поместите `client_id` и `client_secret`, полученные при регистрации клиента, в переменные окружения, а `redirect_uri` зарегистрируйте у этого клиента.

```javascript
import express from 'express'
import crypto from 'node:crypto'

const app = express()
const sessions = new Map() // в продакшене — Redis или БД

const CLIENT_ID = process.env.PARTNER_CLIENT_ID
const CLIENT_SECRET = process.env.PARTNER_CLIENT_SECRET
const REDIRECT_URI = 'https://yourapp.com/callback'

// 1. Кнопка «Подключить Битрикс24» ведёт сюда
app.get('/connect', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex')
  sessions.set(state, { userId: req.user.id, createdAt: Date.now() })

  const params = new URLSearchParams({
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    scope: 'crm task',
    state,
  })
  res.redirect(`https://vibecode.bitrix24.tech/v1/connect/authorize?${params}`)
})

// 2. Вайбкод возвращает пользователя сюда после согласия или отказа
app.get('/callback', async (req, res) => {
  const { code, state, error } = req.query

  const session = sessions.get(state)
  if (!session) return res.status(400).send('Неизвестный state')
  sessions.delete(state)

  if (error === 'access_denied') {
    return res.redirect('/dashboard?connect=denied')
  }

  // 3. Обмениваем код на API-ключ — серверный запрос с client_secret
  const tokenRes = await fetch('https://vibecode.bitrix24.tech/v1/connect/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code,
      redirect_uri: REDIRECT_URI,
    }),
  })
  const data = await tokenRes.json()

  if (!tokenRes.ok) {
    // Ошибка приходит в RFC-форме: { error, error_description }
    return res.status(400).send(`Ошибка обмена: ${data.error}`)
  }

  // 4. Сохраняем привязку: пользователь нашего сервиса ↔ портал Битрикс24
  await db.connections.upsert({
    userId: session.userId,
    portalDomain: data.portal.domain,
    portalName: data.portal.name,
    apiKey: encrypt(data.api_key), // секрет, шифруем перед хранением
    scopes: data.scopes,
  })

  res.redirect('/dashboard?connect=ok')
})

// 5. Дальше используем сохранённый ключ для вызовов от имени пользователя
async function listDeals(userId) {
  const connection = await db.connections.findByUserId(userId)
  const apiKey = decrypt(connection.apiKey)

  const res = await fetch('https://vibecode.bitrix24.tech/v1/deals?limit=10', {
    headers: { 'X-Api-Key': apiKey },
  })
  return res.json()
}
```

Ключевые моменты сценария:

- `state` генерируется на сервере и проверяется при возврате — без этой проверки злоумышленник может подсунуть пользователю чужой код.
- `client_secret` хранится только на сервере и в браузер не попадает.
- API-ключ шифруется перед записью в БД и расшифровывается только в момент вызова.
- При `error=access_denied` пользователь видит понятное сообщение, а не страницу с ошибкой обмена.

## GET /v1/connect/authorize

Перенаправляет пользователя на страницу согласия Битрикс24 Вайбкод.

**Query-параметры:**

| Параметр | Тип | Обяз. | Описание |
|----------|-----|:-----:|----------|
| `client_id` | string | да | Идентификатор партнёра, полученный при регистрации клиента |
| `redirect_uri` | string | да | URL обратного вызова. Должен совпадать с одним из зарегистрированных у клиента |
| `state` | string | да | Произвольная строка, которая вернётся вместе с кодом. Защита от CSRF |
| `scope` | string | нет | Запрашиваемые права через пробел или запятую: `crm task im`. Если не передать — применяются права, зарегистрированные для клиента. Запрос права за пределами зарегистрированного набора отклоняется |

**Пример URL:**

```
https://vibecode.bitrix24.tech/v1/connect/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/callback&scope=crm%20task&state=abc123random
```

**Успешный редирект** после одобрения:

```
https://yourapp.com/callback?code=AUTH_CODE_HERE&state=abc123random
```

**Редирект при отказе** пользователя на странице согласия:

```
https://yourapp.com/callback?error=access_denied&state=abc123random
```

### Ошибки

Ошибки этого эндпоинта приходят двумя разными путями, и обработать нужно оба.

**До того как `redirect_uri` проверен** — обычный ответ `400` с телом RFC 6749. Возвращать пользователя некуда: адрес ещё не подтверждён, редирект на непроверенный URL был бы дырой.

| HTTP | `error` | Условие |
|------|---------|---------|
| 400 | `invalid_request` | Не передан один из `client_id`, `redirect_uri`, `state` |
| 400 | `invalid_client` | `client_id` неизвестен либо клиент отключён |
| 400 | `invalid_request` | `redirect_uri` не совпадает ни с одним зарегистрированным |

```json
{
  "error": "invalid_client",
  "error_description": "Unknown or inactive client"
}
```

**После того как `redirect_uri` проверен** — редирект обратно на него с параметрами `error` и `state` (RFC 6749 §4.1.2.1). Ваш обработчик `redirect_uri` обязан это разбирать.

| `error` в редиректе | Условие |
|---------------------|---------|
| `invalid_scope` | Запрошено право за пределами зарегистрированного набора клиента |
| `invalid_request` | Клиенту требуется PKCE, а `code_challenge` не передан либо метод не `S256` |
| `access_denied` | Пользователь нажал «Отклонить» на странице согласия |

```
https://yourapp.com/callback?error=invalid_scope&state=abc123random
```

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

## POST /v1/connect/token

Обменивает одноразовый код авторизации на постоянный API-ключ. Принимает `application/json` и `application/x-www-form-urlencoded`.

**Поля запроса (body):**

| Поле | Тип | Обяз. | Описание |
|------|-----|:-----:|----------|
| `grant_type` | string | да | Всегда `authorization_code`. Без него эндпоинт отвечает `unsupported_grant_type` |
| `client_id` | string | да | Идентификатор партнёра |
| `client_secret` | string | да | Секрет партнёра. Передавать только с серверной стороны |
| `code` | string | да | Код из параметра `code` редиректа |
| `redirect_uri` | string | да | Тот же `redirect_uri`, что был передан в `/v1/connect/authorize` |

### Примеры

#### curl

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code=RECEIVED_CODE \
  -d redirect_uri=https://yourapp.com/callback
```

#### JavaScript

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/connect/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    code: receivedCode,
    redirect_uri: 'https://yourapp.com/callback',
  }),
})

const data = await res.json()
// data.api_key — постоянный API-ключ Вайбкод
// data.portal — выбранный пользователем портал
// data.scopes — запрошенные скоупы, data.granted_scopes — права ключа
// data.user — пользователь, выдавший доступ
```

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

| Поле | Тип | Описание |
|------|-----|----------|
| `api_key` | string | API-ключ Вайбкод в формате `vibe_api_...`. Передавать в заголовке `X-Api-Key` при последующих вызовах |
| `portal.domain` | string | Домен портала Битрикс24, например `example.bitrix24.ru` |
| `portal.name` | string | Название портала |
| `scopes` | string[] | Права, запрошенные приложением. На странице согласия пользователь подтверждает набор целиком |
| `granted_scopes` | string[] | Права, которые реально несёт выданный ключ. У самостоятельно зарегистрированного партнёрского приложения, как правило, совпадают со `scopes` — разойдутся, если права ключа изменили после согласия |
| `user.name` | string | Имя пользователя, выдавшего доступ |
| `user.email` | string | Электронная почта пользователя |

Два набора прав расходятся только у приложений, которым состав прав назначает сама платформа — например у настольного приложения [Cowork](/docs/cowork). Такое приложение может не запрашивать ничего, тогда `scopes` приходит пустым, а `granted_scopes` перечисляет весь набор ключа. Проверяйте доступ по `granted_scopes`.

Поле `granted_scopes` может отсутствовать в ответе, если платформа не смогла прочитать права выданного ключа. Пустым оно при этом не приходит, поэтому отсутствие поля означает «набор неизвестен», а не «прав нет» — в таком ответе ориентируйтесь на `scopes`.

Набор в `granted_scopes` отражает то, что записано в ключе. Отдельное право портал Битрикс24 может не подтвердить на своей стороне, поэтому отказ разбирайте по коду ответа Битрикс24, а не по наличию права в списке.

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

```json
{
  "api_key": "vibe_api_...",
  "portal": {
    "domain": "example.bitrix24.ru",
    "name": "Моя компания"
  },
  "scopes": ["crm", "task"],
  "granted_scopes": ["crm", "task"],
  "user": {
    "name": "Иван Иванов",
    "email": "ivan@example.com"
  }
}
```

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

400 — не передан обязательный параметр:

```json
{
  "error": "invalid_request",
  "error_description": "Missing client_id, code or redirect_uri"
}
```

### Ошибки

Эндпоинт отвечает в форме RFC 6749 — `{ error, error_description }`, без обёртки `success`. Это сделано ради совместимости с готовыми OAuth-библиотеками.

| HTTP | `error` | Условие |
|------|---------|---------|
| 400 | `invalid_request` | Не передан один из `client_id`, `code`, `redirect_uri` |
| 400 | `invalid_grant` | Код не существует, истёк, уже был использован, либо `redirect_uri` не совпадает с переданным в `/v1/connect/authorize` |
| 400 | `invalid_grant` | Проверка PKCE не прошла: `code_verifier` не соответствует переданному ранее `code_challenge` |
| 400 | `invalid_client` | Публичный клиент прислал `client_secret` — публичному клиенту секрет не выдаётся и передавать его нельзя |
| 400 | `unsupported_grant_type` | `grant_type` не передан или не равен `authorization_code` |
| 401 | `invalid_client` | `client_id` неизвестен, клиент отключён, либо `client_secret` не совпадает |

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

## Публичный клиент: поток с PKCE

Всё выше описывает конфиденциальный клиент — тот, у которого есть сервер и `client_secret`. Если сервера нет и секрет спрятать негде (мобильное приложение, десктопная программа, одностраничное веб-приложение), регистрируйте публичный клиент. Секрет ему не выдаётся, подлинность подтверждается механизмом PKCE.

Отличий от основного потока три, всё остальное совпадает.

**1. Приложение генерирует пару на каждый запуск потока.** Случайная строка (`code_verifier`) и её SHA-256-хеш в base64url без выравнивающих знаков `=` (`code_challenge`).

```javascript
import crypto from 'node:crypto'

const b64url = (buf) => buf.toString('base64url')
const verifier  = b64url(crypto.randomBytes(32))
const challenge = b64url(crypto.createHash('sha256').update(verifier).digest())
```

`verifier` держите до конца потока, `challenge` уходит в первый запрос.

**2. В `/v1/connect/authorize` добавляются два параметра.**

| Параметр | Значение |
|----------|----------|
| `code_challenge` | Хеш из шага 1 |
| `code_challenge_method` | Всегда `S256`. Другие значения отклоняются |

Для публичного клиента они **обязательны**: без них поток завершится редиректом `?error=invalid_request`.

**3. В `/v1/connect/token` вместо `client_secret` уходит `code_verifier`.**

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/connect/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "YOUR_CLIENT_ID",
    "code": "RECEIVED_CODE",
    "redirect_uri": "http://127.0.0.1:9999/callback",
    "code_verifier": "CODE_VERIFIER"
  }'
```

Ответ тот же, что у конфиденциального клиента:

```json
{
  "api_key": "vibe_api_...",
  "scopes": ["crm"],
  "granted_scopes": ["crm"],
  "portal": { "domain": "example.bitrix24.ru", "name": "Моя компания" },
  "user": { "name": "Иван Иванов", "email": "ivan@example.com" }
}
```

**`client_secret` публичному клиенту передавать нельзя** — платформа ответит `400 invalid_client`. Это не придирка: секрет, зашитый в раздаваемое приложение, секретом не является, и поток построен так, чтобы им нельзя было пользоваться.

### Возврат на localhost

У программы без своего сайта нет публичного адреса, куда вернуть код. Для таких случаев разрешён возврат на себя: зарегистрируйте `redirect_uri` вида `http://127.0.0.1:9999/callback`, поднимите на время потока временный слушатель на этом порту и закройте его сразу после получения кода.

Порт при сверке **не учитывается** — если занят, приложение может слушать любой другой, повторно регистрировать адрес не нужно. Годятся `127.0.0.1`, `localhost` и `[::1]`, схема `http` для них разрешена. Все остальные адреса обязаны быть `https`.

## Вход с устройства без браузера

Телевизор, приставка, утилита в терминале — там, где неудобно вводить логин и пароль. Пользователь подтверждает вход на телефоне или компьютере, а доступ получает устройство. Поток описан стандартом RFC 8628.

Устройству нужен разрешённый device-режим: он включается платформенным администратором и только проверенным приложениям. Клиент, зарегистрированный самостоятельно, получит `400 unauthorized_client` — это защита пользователей от того, чтобы код подтверждения показывало непроверенное приложение.

**Шаг 1. Устройство запрашивает код.**

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/connect/device/authorize \
  -H "Content-Type: application/json" \
  -d '{"client_id":"YOUR_CLIENT_ID","scope":"crm","code_challenge":"ХЕШ","code_challenge_method":"S256"}'
```

```json
{
  "device_code": "MYQnuWB3H0tR...",
  "user_code": "54BV-XT8F",
  "verification_uri": "https://vibecode.bitrix24.tech/connect/device",
  "verification_uri_complete": "https://vibecode.bitrix24.tech/connect/device?user_code=54BV-XT8F",
  "expires_in": 899,
  "interval": 5
}
```

`device_code` — секрет устройства, на экран не выводится. `user_code` показывается пользователю вместе с адресом. `expires_in` — сколько секунд у пользователя есть на подтверждение.

**Шаг 2. Пользователь открывает адрес и подтверждает.** На экране подтверждения он видит карточку приложения, запрошенные права, выбор портала и предупреждение о фишинге: подтверждать следует только тот вход, который начал он сам.

**Шаг 3. Устройство опрашивает `/v1/connect/token`.**

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/connect/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "client_id": "YOUR_CLIENT_ID",
    "device_code": "DEVICE_CODE",
    "code_verifier": "CODE_VERIFIER"
  }'
```

Пока пользователь не подтвердил — `400` с одним из состояний:

| `error` | Что делать |
|---------|------------|
| `authorization_pending` | Ждать и опрашивать дальше с интервалом `interval` |
| `slow_down` | Опрос идёт чаще разрешённого. Увеличить интервал на 5 секунд и продолжать |
| `access_denied` | Пользователь отклонил. Прекратить опрос |
| `expired_token` | Код истёк. Запросить новый с шага 1 |

После подтверждения тот же запрос отдаёт `200` с ключом и двумя наборами прав:

```json
{ "api_key": "vibe_api_...", "scopes": ["crm"], "granted_scopes": ["crm"] }
```

Обратите внимание: здесь объект **уже**, чем в основном потоке, — без блоков `portal` и `user`.

Ключ отдаётся **один раз**. Повторный опрос тем же `device_code` вернёт `expired_token`, поэтому сохраняйте ключ сразу.

### Что обязано уметь устройство

- **Показывать обратный отсчёт.** Код живёт 15 минут; по истечении показать новый, а не мёртвый экран.
- **Соблюдать `interval` и реагировать на `slow_down`.** Опрос чаще разрешённого не ускорит выдачу, а приведёт к отказам.
- **Не показывать `device_code`.** На экран идёт только `user_code`.

## Использование API-ключа

Полученный ключ имеет тип `APP` и работает как стандартный API-ключ Вайбкод. Передавайте его в заголовке `X-Api-Key`:

```bash
curl -H "X-Api-Key: vibe_api_..." \
  https://vibecode.bitrix24.tech/v1/deals
```

Один и тот же ключ можно использовать с любым эндпоинтом Вайбкод API в пределах подтверждённых скоупов: список сделок, batch-запросы, агрегация и т. д.

Партнёрскому ключу платформенные скоупы `vibe:ai` (AI-роутер) и `vibe:search` (веб-поиск) **не** добавляются — ключ несёт ровно те права, которые пользователь подтвердил на странице согласия. Поэтому вызовы AI-роутера (`/v1/chat/completions`) и веб-поиска (`/v1/search`) через партнёрский ключ по умолчанию возвращают `403`.

Партнёрский ключ работает с одним заголовком `X-Api-Key` и **не требует** `Authorization: Bearer <session_token>`. По префиксу его видно сразу: партнёрский ключ начинается с `vibe_api_`, а ключ OAuth-приложения из каталога Вайбкод — с `vibe_app_`, и для второго сессия пользователя обязательна.

## Доступные скоупы

При вызове `/v1/connect/authorize` передавайте те же скоупы, что и при создании стандартного API-ключа в портале Битрикс24. На странице согласия пользователь видит список запрошенных прав и подтверждает его целиком — снять отдельный пункт нельзя, доступны только «Разрешить» и «Отклонить». Итоговый набор приходит в поле `granted_scopes` ответа `/v1/connect/token`, а запрошенный — в поле `scopes` рядом.

Актуальный перечень поддерживаемых значений описан в разделе [Ключи и авторизация](/docs/keys-auth) — он пополняется по мере появления новых модулей Битрикс24, поэтому здесь не дублируется.

Платформенные права Вайбкод (`vibe:ai`, `vibe:search`, `vibe:infra`, `vibe:storage`, `vibe:feedback`) через саморегистрацию запросить нельзя — см. [Регистрация партнёра](#регистрация-партнёра).

## Время жизни и отзыв ключа

- **Код авторизации (`code`)** действует **5 минут** и одноразовый. После обмена через `/v1/connect/token` повторный вызов с тем же кодом вернёт `invalid_grant`.
- **Стейт согласия** (внутреннее состояние страницы согласия) живёт 10 минут — если пользователь не подтвердит за это время, ссылку придётся выдавать заново.
- **API-ключ** не имеет срока истечения. Действует, пока его не отзовут. Отзыв происходит одним из трёх способов:

  1. **Пользователь отзывает доступ сам** — раздел «Подключённые приложения» в его профиле Вайбкод. Это гасит **все** ключи, которые он выдал вашему приложению для этого портала, включая ключи от прежних авторизаций. Доступы других сотрудников того же портала не затрагиваются.
  2. **Владелец приложения полностью удаляет клиента** — вызовом `DELETE /api/connect/clients/:id?purge=true` (клиент должен быть предварительно деактивирован); гасятся все ключи, выданные этим клиентом. В кабинете доступна только деактивация — ключи она не трогает.
  3. **Платформенный администратор** отзывает выданные клиентом ключи.

  **Деактивация клиента ключи НЕ отзывает** — она только закрывает выдачу новых: с неактивным клиентом `/v1/connect/authorize` отклонит запрос, а уже выданные ключи продолжат работать.

При отзыве ключа все запросы с ним возвращают `401 KEY_INACTIVE`. Если ключ удалён или неизвестен — `401 INVALID_API_KEY`. Партнёр должен корректно обработать оба кода и предложить пользователю повторно авторизоваться.

## Безопасность

- **State-параметр** — генерируйте криптостойкую случайную строку на каждый запрос и сверяйте при возврате на `redirect_uri`. Защищает от CSRF.
- **`client_secret` — только на сервере.** Никогда не передавайте секрет в браузер, мобильное приложение или клиентский код. Обмен кода на ключ выполняется только серверным запросом.
- **Хранение ключа.** API-ключ — секрет, который даёт доступ к данным пользователя. Храните в зашифрованном виде, ограничивайте доступ к строке подключения, не логируйте.
- **Привязка `redirect_uri`.** Значение в `/v1/connect/authorize` и `/v1/connect/token` должно совпадать символ в символ — несоответствие приведёт к `invalid_grant`.

## Регистрация партнёра

Клиент регистрируется самостоятельно в кабинете Вайбкод — раздел [Connect-приложения](/connect-apps), боковое меню, группа «Доступ». Войдите под учётной записью Вайбкод и нажмите «Зарегистрировать приложение» — `client_id` выдаётся сразу, без заявки и предварительной проверки.

В форме заполняются:

- название и краткое описание приложения,
- идентификатор — строчные латинские буквы, цифры и дефис,
- один или несколько `redirect_uri` — адреса `https://` либо локальные `127.0.0.1` и `localhost`,
- запрашиваемые права доступа.

Набор прав не может быть пустым: регистрация без единого права возвращает `EMPTY_SCOPES`. Приложение без прав не может запросить у пользователя ничего.

Тип клиента выбирается при создании и позже не меняется — разбор с примерами в разделе [Типы клиента](#типы-клиента).

`client_secret` показывается один раз при создании. Прочитать его повторно нельзя, только перевыпустить действием «Обновить секрет».

В ⋮-меню карточки приложения есть два помощника: **«Собрать ссылку»** — конструктор ссылки авторизации (выбор `redirect_uri` и прав, готовый URL и `curl` для обмена кода, для клиента без секрета — одноразовая пара строк PKCE), и **«Как увидит клиент»** — превью страницы согласия ровно в том виде, в каком её увидит пользователь, включая бейдж верификации.

Платформенные скоупы `vibe:ai`, `vibe:search`, `vibe:infra`, `vibe:storage` и `vibe:feedback` при саморегистрации недоступны — запрос с ними отклоняется кодом `SCOPE_NOT_ALLOWED`. Их выдаёт платформенный администратор проверенному приложению.

Число активных клиентов на одного пользователя ограничено. При достижении предела регистрация возвращает `CLIENT_LIMIT_REACHED`, текущее значение приходит в поле `limit`. Деактивация клиента освобождает место.

Новое приложение получает статус «непроверенное» — пользователь видит эту отметку на странице согласия. Повысить статус может платформенный администратор, запрос отправляется действием «Запросить верификацию» в карточке приложения. Заявку могут отклонить с причиной — она приходит владельцу приложения, и после правок заявку подают заново.

### Что снимает отметку «проверено»

Отметка держится на том, каким приложение прошло проверку, поэтому правка четырёх полей возвращает статус «непроверенное»:

- название,
- логотип,
- адреса возврата `redirect_uri`,
- набор запрашиваемых прав.

Описание и адрес сайта на отметку не влияют.

Кабинет предупреждает до сохранения: форма показывает подтверждение с перечнем полей, из-за которых отметка будет снята. Без подтверждения правка не сохраняется, ответ — `VERIFICATION_RESET_NOT_CONFIRMED` с тем же перечнем в теле.

Заполнение пустого набора прав отметку не снимает, когда выбранные права укладываются в набор, выданный администратором.

Сброс касается только отметки. Выданные ключи, подключённые пользователи, платформенные права и разрешённый вход по коду устройства сохраняются. Заявку на проверку подают заново.

## Типы клиента

Тип отвечает на один вопрос: есть ли у приложения место, недоступное пользователю, где можно хранить секрет. От ответа зависит, выдаётся ли `client_secret`.

### Конфиденциальный

У приложения есть серверная часть. `client_secret` лежит там и никогда не попадает в браузер или на устройство пользователя.

Примеры: SaaS-сервис аналитики, который ходит в портал клиента по расписанию. Синхронизация с 1С на вашем сервере. Чат-ассистент, который принимает вебхуки и отвечает от своего бэкенда.

Этот тип нужен для потока, описанного на этой странице: обмен кода на ключ выполняется серверным запросом с `client_secret`.

### Публичный

Секрет спрятать негде — код целиком выполняется на устройстве пользователя. `client_secret` не выдаётся, вместо него подлинность подтверждает PKCE: приложение генерирует на каждый запуск потока случайную строку, отправляет в `/v1/connect/authorize` её хеш, а в `/v1/connect/token` — саму строку. Перехваченный код без неё не обменивается.

Примеры: мобильное приложение. Десктопная программа. Одностраничное веб-приложение без собственного бэкенда.

### Как выбрать

Если у приложения есть сервер, который вы контролируете, — конфиденциальный. Если код целиком уезжает пользователю — публичный.

Тип задаётся один раз при регистрации. Чтобы сменить его, зарегистрируйте нового клиента.

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

- [Ключи и авторизация](/docs/keys-auth)
- [Управляющие ключи](/docs/management-keys)
- [Ошибки API](/docs/errors)
