Для AI-агентов: markdown этой страницы — /docs-content/partner-connect.md индекс документации — /llms.txt
Partner Connect
Подключение внешних сервисов к Битрикс24: ваше приложение запрашивает у пользователя разрешение на доступ к его порталу и получает API-ключ Вайбкод для дальнейших вызовов. Поток построен по схеме Authorization Code — той же, что у OAuth-провайдеров Google или GitHub.
Базовый URL: https://vibecode.bitrix24.tech/v1 | Авторизация: client_id + client_secret партнёра | Скоупы ключа: запрашиваются на странице согласия
Содержание
- Режим подключения
- Опрос подключения Компании
- Когда применять
- Как это работает
- Справочник эндпоинтов
- Полный сценарий — Express-обработчик от и до
- Подключение AI из приложения внутри Битрикс24 — кнопка и согласие в отдельном окне
GET /v1/connect/authorize— старт потока, редирект на согласиеPOST /v1/connect/token— обмен кода на API-ключ- Публичный клиент: поток с PKCE — приложение без сервера
- Вход с устройства без браузера — телевизор, приставка, терминал
- Использование API-ключа
- Доступные скоупы
- Время жизни и отзыв ключа
- Безопасность
- Регистрация партнёра
- Типы клиента — публичный или конфиденциальный, с примерами
- Смотрите также
Режим подключения
Новые клиенты используют режим PERSONAL: согласие и ключ принадлежат пользователю. Режим COMPANY подготавливает общее подключение компании к облачному аккаунту Битрикс24; его устанавливает только администратор платформы. Он требует клиента CONFIDENTIAL, секрета клиента и PKCE S256. Включённый вход по коду устройства несовместим с COMPANY; попытка включить вторую возможность отклоняется. Переключение возможно, пока клиент не выдал ни одного ключа: история попыток без выдачи его не запрещает. Первая выдача закрепляет режим навсегда, даже после отзыва ключей; смена даёт HTTP 409 CONNECT_COMPANY_MODE_LOCKED — для режима Компании зарегистрируйте нового клиента. Незавершённые попытки отменяются при разрешённом переключении. Существующее подключение Компании запрещает переход обратно в PERSONAL.
Выдача для COMPANY пока закрыта. GET /v1/connect/authorize возвращает на зарегистрированный redirect_uri ошибку access_denied, исходный state и error_description=CONNECT_COMPANY_NOT_READY независимо от доступности подготовки. Код авторизации не создаётся. POST /v1/connect/token с grant_type=authorization_code и корректными учётными данными такого клиента возвращает HTTP 403 с тем же кодом в code и error_description.
Запуск и обмен device-кода для COMPANY возвращают HTTP 400, error=unauthorized_client, code=CONNECT_COMPANY_DEVICE_FLOW_FORBIDDEN. Подтверждение согласия из сессии поддержки, действующей от имени другого пользователя, отклоняется с CONNECT_COMPANY_IMPERSONATION_FORBIDDEN, в том числе на разрешённом для поддержки аккаунте. Повторная попытка и выбор другого аккаунта эти ограничения не устраняют. Описанные ниже сценарии выдачи относятся к PERSONAL.
Для секрета COMPANY хранилище закрыто с HTTP 403 CONNECT_COMPANY_STORAGE_NOT_READY. Выпуск производных токенов и ключей, а также управление ключами внешних моделей закрыты с HTTP 403 CONNECT_COMPANY_DERIVED_TOKEN_FORBIDDEN. Неизвестные маршруты отклоняются с HTTP 403 CONNECT_COMPANY_ROUTE_FORBIDDEN. Эти отказы не снимаются выбором другого портала. Выдача в проде остаётся закрытой независимо от флага.
Опрос подключения Компании
GET /v1/connect/company/status позволяет серверу Продукта проверить своё подключение по уже выданному секрету COMPANY. Передайте его только в заголовке X-Api-Key. Секрет клиента, cookie и Bearer для этого метода не подходят. Храните секрет подключения на сервере, не передавайте в браузер и не записывайте в журналы. Пара аккаунта и Продукта определяется секретом. Метод не принимает параметры запроса, включая идентификаторы другой пары.
Запрос
curl --max-time 20 "$VIBE_URL/v1/connect/company/status" \
-H "X-Api-Key: $VIBE_COMPANY_SECRET"
const response = await fetch(`${process.env.VIBE_URL}/v1/connect/company/status`, {
headers: { 'X-Api-Key': process.env.VIBE_COMPANY_SECRET },
cache: 'no-store',
signal: AbortSignal.timeout(20000),
})
const result = await response.json()
if (!response.ok) throw new Error(result.error.code)
console.log(result.data.status, result.data.revision, result.data.action)
Ответ и ошибки возвращаются с Cache-Control: no-store. Пример ответа для подключения, ожидающего администратора:
{
"success": true,
"data": {
"connection_id": "connection-id",
"connection_mode": "COMPANY",
"status": "NEEDS_ADMIN",
"generation": 0,
"revision": 1,
"credential_version": 1,
"bitrix_ready": false,
"reason_code": "CONNECTION_ADMIN_REQUIRED",
"webhook_cleanup": "none",
"capabilities": { "bitrix": false, "ai": false, "storage": false },
"poll_after_seconds": 30,
"action": "AUTHORIZE_ADMIN"
}
}
Поля ответа
| Поле | Тип | Значение |
|---|---|---|
success |
boolean | true для успешного опроса |
data |
object | Диагностика собственного подключения |
data.connection_id |
string | Идентификатор подключения, полученный этим методом |
data.connection_mode |
string | Всегда COMPANY |
data.status |
string | ACTIVE, NEEDS_ADMIN, SUSPENDED, DISCONNECTED, DELETION_PENDING |
data.generation |
integer | Поколение согласия подключения |
data.revision |
integer | Записанная версия состояния подключения |
data.credential_version |
integer | Версия текущего секрета подключения |
data.bitrix_ready |
boolean | Пока всегда false |
data.reason_code |
string | Причина закрытого доступа, см. таблицу ниже |
data.webhook_cleanup |
string | none — нет незавершённых попыток, pending — очистка или создание не завершены, attention_required — результат создания вебхука неизвестен |
data.capabilities.bitrix |
boolean | Пока всегда false |
data.capabilities.ai |
boolean | Пока всегда false |
data.capabilities.storage |
boolean | Пока всегда false |
data.poll_after_seconds |
integer | Рекомендуемый интервал опроса: 30 секунд |
data.action |
string | WAIT, AUTHORIZE_ADMIN, CONTACT_SUPPORT, RECONNECT, WAIT_FOR_DELETION |
| Состояние или условие | reason_code |
action |
|---|---|---|
ACTIVE, подтверждение администратора действительно |
CONNECT_COMPANY_NOT_READY |
WAIT |
ACTIVE, подтверждение истекло, отозвано или отсутствует |
ADMIN_CHECK_REQUIRED |
AUTHORIZE_ADMIN |
NEEDS_ADMIN |
CONNECTION_ADMIN_REQUIRED |
AUTHORIZE_ADMIN |
SUSPENDED |
CONNECTION_SUSPENDED |
CONTACT_SUPPORT |
DISCONNECTED |
CONNECTION_DISCONNECTED |
RECONNECT |
DELETION_PENDING |
CONNECTION_DELETION_PENDING |
WAIT_FOR_DELETION |
Опрос не продлевает подтверждение администратора. Значение ACTIVE само по себе не открывает доступ: сейчас Битрикс24 и искусственный интеллект (AI) возвращают CONNECT_COMPANY_NOT_READY, файлы — CONNECT_COMPANY_STORAGE_NOT_READY. В ответе нет персональных данных, перечня людей, секретов и URL.
Ошибки и повторение опроса
| HTTP | Код | Что делать |
|---|---|---|
| 401 | INVALID_API_KEY |
Проверить текущий секрет. Неизвестный, отозванный, старый после ротации секрет и запрос чужой пары дают одинаковый отказ |
| 403 | CLIENT_BLOCKED |
Продукт заблокирован, обратиться в поддержку |
| 403 | PORTAL_DELETED |
Аккаунт удалён, остановить опрос |
| 403 | PORTAL_BLOCKED |
Аккаунт заблокирован, обратиться в поддержку |
| 403 | PORTAL_SUSPENDED |
Аккаунт приостановлен, обратиться в поддержку |
| 429 | RATE_LIMITED |
Достигнут лимит IP. Подождать не меньше Retry-After секунд |
| 429 | CALLER_RATE_LIMITED |
Достигнут общий лимит подключения или аккаунта. Подождать не меньше Retry-After секунд |
Пример одинакового отказа для недействительного секрета:
{ "success": false, "error": { "code": "INVALID_API_KEY", "message": "Invalid API key" } }
Опрашивайте не чаще poll_after_seconds, при 429 увеличивайте паузу. Базовый лимит IP — 120 запросов в минуту; действующий порог указан в x-ratelimit-limit. Успешный опрос также расходует общий лимит подключения и аккаунта вместе с другими API-запросами. Ротация секрета не сбрасывает этот лимит.
Секрет в состоянии STATUS_ONLY позволяет читать диагностику, но не вызывать рабочие методы. REVOKED не позволяет даже опрос. После ротации используйте новый секрет. Уже выданная защита и опрос действуют независимо от доступности новой выдачи. Повторное подключение выполняет администратор аккаунта через отдельное согласие. Коды ошибок описаны также в справочнике ошибок.
Когда применять
Сценарий — внешний SaaS-продукт, который интегрируется с порталами Битрикс24 ваших клиентов: CRM-аналитика, синхронизация с 1С, чат-ассистент, конструктор лендингов. Кнопка «Подключить Битрикс24» на вашем сайте запускает поток: пользователь выбирает портал и подтверждает скоупы, ваш сервер получает постоянный API-ключ и работает с порталом от имени пользователя.
Если вы пишете приложение, которое ставится внутри портала Битрикс24 и работает только с ним, — используйте обычный API-ключ, см. Ключи и авторизация.
Как это работает
Ваше приложение → [1. Redirect] → Вайбкод Consent Page → [2. Согласие]
↑ ↓
└──── [4. API-ключ] ←── [3. Code → redirect_uri] ──────────┘
- Redirect — отправляете пользователя на
/v1/connect/authorizeсclient_id,redirect_uri,stateи списком скоупов. - Согласие — пользователь видит страницу Вайбкод, выбирает портал и подтверждает или отклоняет запрошенные скоупы.
- Код — после одобрения пользователь возвращается на
redirect_uriс параметрамиcodeиstate. При отказе —redirect_uri?error=access_denied&state=.... - Обмен — сервер партнёра отправляет
POST /v1/connect/tokenи получает API-ключ.
Справочник эндпоинтов
| Метод | Путь | Описание |
|---|---|---|
| GET | /v1/connect/authorize |
Старт потока: редирект на страницу согласия |
| POST | /v1/connect/token |
Обмен одноразового кода на постоянный API-ключ |
| GET | /v1/connect/company/status |
Диагностика собственного подключения Компании |
Полный сценарий
Пример Express-обработчика, который проводит пользователя через оба эндпоинта от и до. Перед запуском поместите client_id и client_secret, полученные при регистрации клиента, в переменные окружения, а redirect_uri зарегистрируйте у этого клиента.
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пользователь видит понятное сообщение, а не страницу с ошибкой обмена.
Подключение AI из приложения внутри Битрикс24
Приложение, которое открывается во фрейме портала Битрикс24, не может показать у себя страницу согласия: страница согласия запрещает встраивание в чужие сайты заголовками X-Frame-Options: SAMEORIGIN и Content-Security-Policy: frame-ancestors 'self'. Так её нельзя подменить или спрятать под чужим интерфейсом. Поэтому кнопка «Подключить AI» открывает согласие в отдельном окне браузера, а фрейм ждёт результат от вашего сервера.
Порядок такой:
- Кнопка во фрейме открывает окно на адресе вашего сервера и передаёт в нём одноразовый идентификатор попытки
ticket. - Сервер запоминает попытку и перенаправляет окно на
/v1/connect/authorizeс правомvibe:ai. - Пользователь выбирает портал и подтверждает доступ. Вайбкод возвращает окно на ваш
redirect_uri. - Сервер обменивает код на ключ, отмечает попытку завершённой и закрывает окно.
- Фрейм опрашивает сервер по
ticketи показывает результат.
Код во фрейме приложения:
<button id="connect-ai" type="button">Подключить AI</button>
<p id="connect-ai-status" role="status"></p>
<script>
const button = document.getElementById('connect-ai')
const statusLine = document.getElementById('connect-ai-status')
button.addEventListener('click', () => {
// Окно открывается прямо в обработчике клика: окно, открытое после await, браузер блокирует
const ticket = crypto.randomUUID()
const popup = window.open(
`/connect/ai/start?ticket=${ticket}`,
'vibe-connect-ai',
'popup,width=640,height=760',
)
if (!popup) {
statusLine.textContent = 'Разрешите всплывающие окна для этого приложения и нажмите кнопку ещё раз'
return
}
statusLine.textContent = 'Подтвердите доступ в открывшемся окне'
waitForResult(ticket)
})
async function waitForResult(ticket) {
const deadline = Date.now() + 10 * 60 * 1000 // 10 минут на согласие
while (Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 2000))
const res = await fetch(`/connect/ai/status?ticket=${ticket}`)
const result = await res.json()
if (result.status === 'ok') {
statusLine.textContent = 'AI подключён'
return
}
if (result.status === 'denied') {
statusLine.textContent = `AI не подключён: ${result.reason}`
return
}
}
statusLine.textContent = 'Подключение не завершено. Нажмите кнопку ещё раз'
}
</script>
Сервер приложения делает тот же обмен кода, что в полном сценарии, и хранит состояние попытки для фрейма. db и encrypt — ваши хранилище и шифрование. Адрес https://yourapp.com/connect/ai/callback зарегистрируйте у клиента как redirect_uri, а право vibe:ai должно входить в права клиента — см. Платформенные права.
import express from 'express'
import crypto from 'node:crypto'
const app = express()
const attempts = new Map() // ticket → результат попытки; в продакшене — Redis или БД
const states = new Map() // state → ticket
const CLIENT_ID = process.env.PARTNER_CLIENT_ID
const CLIENT_SECRET = process.env.PARTNER_CLIENT_SECRET
const REDIRECT_URI = 'https://yourapp.com/connect/ai/callback'
const TICKET_RE = /^[0-9a-f-]{36}$/
// 1. Окно, открытое кнопкой, приходит сюда и уходит на страницу согласия
app.get('/connect/ai/start', (req, res) => {
const { ticket } = req.query
if (typeof ticket !== 'string' || !TICKET_RE.test(ticket) || attempts.has(ticket)) {
return res.status(400).send('Некорректная попытка подключения')
}
attempts.set(ticket, { status: 'pending' })
const state = crypto.randomBytes(16).toString('hex')
states.set(state, ticket)
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: 'vibe:ai',
state,
})
res.redirect(`https://vibecode.bitrix24.tech/v1/connect/authorize?${params}`)
})
// 2. Вайбкод возвращает окно сюда после согласия или отказа
app.get('/connect/ai/callback', async (req, res) => {
const { code, state, error, error_description: reason } = req.query
const ticket = states.get(state)
if (!ticket) return res.status(400).send('Неизвестный state')
states.delete(state)
if (error) {
// error_description несёт машинный код причины — см. «Отказ после экрана согласия»
attempts.set(ticket, { status: 'denied', reason: reason ?? error })
return res.send(closeWindowPage('Доступ не выдан. Окно можно закрыть'))
}
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) {
attempts.set(ticket, { status: 'denied', reason: data.error })
return res.send(closeWindowPage('Ключ не получен. Окно можно закрыть'))
}
await db.connections.upsert({
portalDomain: data.portal.domain,
apiKey: encrypt(data.api_key), // секрет, шифруем перед хранением
scopes: data.scopes,
})
attempts.set(ticket, { status: 'ok' })
res.send(closeWindowPage('AI подключён. Окно можно закрыть'))
})
// 3. Фрейм спрашивает результат попытки
app.get('/connect/ai/status', (req, res) => {
res.json(attempts.get(req.query.ticket) ?? { status: 'unknown' })
})
function closeWindowPage(text) {
return `<!doctype html><meta charset="utf-8"><p>${text}</p><script>window.close()</script>`
}
Ключевые моменты:
- Окно открывается синхронно в обработчике клика. Окно, открытое после
await, браузер блокирует как всплывающее. - Результат фрейм берёт у своего сервера по
ticket, а не у окна: окно уходит на другие сайты, и опрос своего сервера не зависит от того, что с окном происходит по дороге. ticket— не секрет: по нему видно только состояние попытки. Ключ остаётся на сервере и во фрейм не попадает.- Опрос ограничен по времени: пользователь может закрыть окно, не ответив, и тогда на
redirect_uriне придёт ничего. - Отказ приходит в
errorиerror_description, фрейм показывает код причины как есть. Разбор кодов — Отказ после экрана согласия.
GET /v1/connect/authorize
Перенаправляет пользователя на страницу согласия Битрикс24 Вайбкод.
Query-параметры:
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
client_id |
string | да | Идентификатор партнёра, полученный при регистрации клиента |
redirect_uri |
string | да | URL обратного вызова. Должен совпадать с одним из зарегистрированных у клиента |
state |
string | да | Произвольная строка, которая вернётся вместе с кодом. Защита от CSRF |
scope |
string | нет | Запрашиваемые права через пробел или запятую: crm task im. Если не передать — применяются права, зарегистрированные для клиента. Запрос права за пределами зарегистрированного набора отклоняется |
code_challenge |
string | условно | Хеш SHA-256 от code_verifier в base64url без =. Обязателен для публичного клиента или при обязательном PKCE, иначе необязателен |
code_challenge_method |
string | условно | S256, обязателен вместе с code_challenge. Другие методы, пустые значения и метод без хеша отклоняются |
Для клиента с секретом PKCE необязателен, если не включён в настройках клиента. Если передать code_challenge, платформа сохранит его и потребует соответствующий code_verifier при обмене кода. PKCE дополняет client_secret, а не заменяет его. Для публичного клиента и при обязательном PKCE хеш нужен всегда.
Пример 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 искажён: не разбирается как URL, содержит фрагмент #, логин или пароль. error_description — Redirect URI is malformed |
| 400 | invalid_request |
Схема redirect_uri не разрешена для его адреса: не https у адреса не на localhost, не http и не https у localhost. error_description — Redirect URI scheme is not allowed for this host |
| 400 | invalid_request |
redirect_uri не совпадает ни с одним зарегистрированным. error_description — Redirect URI not registered |
{
"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 или code_challenge_method. Поддерживается только S256 |
access_denied |
Пользователь нажал «Отклонить» на странице согласия |
access_denied |
Пользователь подтвердил доступ, но ключ выдать не удалось: у портала нет подписки или тарифа, открывающего доступ к Вайбкод, регион не поддерживается либо исчерпан лимит ключей. Машинный код причины приходит в error_description |
temporarily_unavailable |
Пользователь подтвердил доступ, но выписка ключа не завершилась из-за временной недоступности Битрикс24. Попытку можно повторить |
https://yourapp.com/callback?error=invalid_scope&state=abc123random
Превышение частоты — 429 до всякой проверки параметров, поэтому ни редиректа, ни state не будет. Лимит: 30 запросов в минуту, считается по паре client_id и адрес вызывающего. Точное значение — в заголовке x-ratelimit-limit (потолок делится на реплики). Тело приходит в общем конверте API — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } }, а не в форме RFC 6749, и заголовки X-RateLimit-* присутствуют.
Обработать этот путь нужно отдельно, потому что authorize открывается переходом браузера: пользователь увидит страницу с JSON вместо экрана согласия, на redirect_uri не придёт ничего, и приложение не получит обратно свой state. Приложение обязано считать такую авторизацию незавершённой по своему таймауту, а не ждать колбэка. Значение Retry-After называет минимальную паузу.
Отказ после экрана согласия
Отказ в выдаче ключа наступает уже после того, как пользователь подтвердил доступ. Причину он видит на странице согласия Вайбкод, а в приложение возвращается по кнопке — тогда на redirect_uri приходит error, error_description и исходный state:
https://yourapp.com/callback?error=access_denied&error_description=MARKETPLACE_REQUIRED&state=abc123random
error_description несёт машинный код причины, по которому приложение может показать свой экран:
Код в error_description |
Что произошло |
|---|---|
MARKETPLACE_REQUIRED |
На портале нет активной подписки BitrixGPT + Маркетплейс. Там, где доступ открывает тариф Битрикс24, вместо него приходят BY_PAID_ONLY, KZ_PAID_ONLY и UZ_PAID_ONLY |
KEY_LIMIT_REACHED |
Ключ выдать нельзя: исчерпан лимит ключей пользователя или портальный потолок платформы — см. Лимит ключей |
BITRIX_UNAVAILABLE |
Битрикс24 не ответил во время выписки ключа |
STATE_EXPIRED |
Ссылка согласия протухла до подтверждения — авторизацию начинают заново с /v1/connect/authorize |
admin_approval_requested |
Пользователь не администратор портала и попросил администраторов разрешить приложению AI — запрос пришёл им в чат бота. Когда администратор разрешит AI, авторизацию начинают заново с /v1/connect/authorize |
Список кодов открыт: приложение должно понимать любой error_description как причину отказа и не полагаться на то, что перечень выше исчерпывающий.
При отказе в доступе по денежной причине пользователь видит встроенную карточку оплаты на странице согласия. Действия относятся к выбранному порталу Битрикс24. Для MARKETPLACE_REQUIRED карточка предлагает «Узнать подробнее» — открыть описание подписки в новой вкладке, а также активировать пробный период, если он доступен этому порталу. Для BY_PAID_ONLY, KZ_PAID_ONLY, UZ_PAID_ONLY и INT_TARIFF_REQUIRED предлагается выбрать тариф Битрикс24; для INT_VIBE_PLUS_REQUIRED — тариф Vibe+. При INT_TARIFF_REQUIRED на .com в зависимости от выбранного портала также может предлагаться Vibe+. В модели Vibe+ карточка предлагает активацию пробного периода, если он доступен; демо-тариф Битрикс24 подключают в самом Битрикс24, если он там доступен.
Пока пользователь не нажмёт кнопку возврата в приложение, на redirect_uri ничего не приходит. Возврат после такого отказа передаёт error=access_denied, машинный код причины в error_description и исходный state; code и API-ключ не выдаются. После покупки или активации пробного периода вернитесь в приложение и начните авторизацию заново с /v1/connect/authorize: одноразовое состояние прежней ссылки уже использовано.
Полный справочник общих ошибок API — Ошибки.
POST /v1/connect/token
Обменивает одноразовый код авторизации на постоянный API-ключ. Принимает application/json и application/x-www-form-urlencoded.
Поля запроса (body):
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
grant_type |
string | да | Для этого потока — authorization_code. Без него эндпоинт отвечает unsupported_grant_type. Другие режимы той же ручки: urn:ietf:params:oauth:grant-type:device_code (см. Вход с устройства без браузера) и обмен кода входа в Коворк urn:vibecode:params:oauth:grant-type:desktop-handoff с полями grant и device_id — описан на странице «Вход в Коворк по коду» (/docs/cowork/desktop-handoff), только на .tech |
client_id |
string | да | Идентификатор партнёра |
client_secret |
string | да | Секрет партнёра. Передавать только с серверной стороны |
code |
string | да | Код из параметра code редиректа |
redirect_uri |
string | да | Тот же redirect_uri, что был передан в /v1/connect/authorize |
code_verifier |
string | условно | Исходная строка PKCE. Обязательна, если при авторизации передан code_challenge, в том числе для клиента с секретом |
Примеры
curl
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
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. Такое приложение может не запрашивать ничего, тогда scopes приходит пустым, а granted_scopes перечисляет весь набор ключа. Проверяйте доступ по granted_scopes.
Поле granted_scopes может отсутствовать в ответе, если платформа не смогла прочитать права выданного ключа. Пустым оно при этом не приходит, поэтому отсутствие поля означает «набор неизвестен», а не «прав нет» — в таком ответе ориентируйтесь на scopes.
Набор в granted_scopes отражает то, что записано в ключе. Отдельное право портал Битрикс24 может не подтвердить на своей стороне, поэтому отказ разбирайте по коду ответа Битрикс24, а не по наличию права в списке.
Пример ответа
{
"api_key": "vibe_api_...",
"portal": {
"domain": "example.bitrix24.ru",
"name": "Моя компания"
},
"scopes": ["crm", "task"],
"granted_scopes": ["crm", "task"],
"user": {
"name": "Иван Иванов",
"email": "ivan@example.com"
}
}
Пример ответа при ошибке
400 — не передан обязательный параметр:
{
"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 не совпадает |
| 429 | slow_down |
Превышен лимит частоты на входе в платформу. Лимит считается по адресу вызывающего и общий для обмена кода и опроса устройства. Ответ несёт Retry-After с минимальной паузой в секундах и не несёт заголовков X-RateLimit-* |
Ограничителей частоты два, и оба отвечают 429. Различать их по заголовку X-RateLimit-Limit. Отказ на входе в платформу из таблицы выше этого заголовка не несёт и приходит в форме RFC 6749. Отказ самого эндпоинта его несёт, а тело приходит в общем конверте API — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } }. Действие в обоих случаях одно: подождать и повторить, увеличив паузу.
Полный справочник общих ошибок API — Ошибки.
Публичный клиент: поток с PKCE
Всё выше описывает конфиденциальный клиент — тот, у которого есть сервер и client_secret. Если сервера нет и секрет спрятать негде (мобильное приложение, десктопная программа, одностраничное веб-приложение), регистрируйте публичный клиент. Секрет ему не выдаётся, подлинность подтверждается механизмом PKCE.
Отличий от основного потока три, всё остальное совпадает.
1. Приложение генерирует пару на каждый запуск потока. Случайная строка (code_verifier) и её SHA-256-хеш в base64url без выравнивающих знаков = (code_challenge).
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.
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"
}'
Ответ тот же, что у конфиденциального клиента:
{
"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. Все остальные адреса обязаны быть https. Схема и путь сверяются всегда, а порт и сам адрес localhost — в зависимости от типа клиента:
| Тип клиента | Как сверяется адрес на localhost |
|---|---|
| Публичный | Порт не учитывается — если занят, приложение может слушать любой другой, повторно регистрировать адрес не нужно. Адреса 127.0.0.1, localhost и [::1] взаимозаменяемы: зарегистрированный http://127.0.0.1/callback принимает и http://localhost:5123/callback |
| Конфиденциальный | Адрес сверяется целиком, включая порт. Так серверное приложение отлаживают на своём компьютере: зарегистрируйте адрес с портом, на котором слушает сервер, например http://localhost:3030/callback |
Имя localhost может указывать не на этот компьютер, если его переопределили в настройках сети, поэтому надёжнее 127.0.0.1.
Вход с устройства без браузера
Телевизор, приставка, утилита в терминале — там, где неудобно вводить логин и пароль. Пользователь подтверждает вход на телефоне или компьютере, а доступ получает устройство. Поток описан стандартом RFC 8628.
Устройству нужен разрешённый device-режим: он включается платформенным администратором и только проверенным приложениям. Клиент, зарегистрированный самостоятельно, получит 400 unauthorized_client — это защита пользователей от того, чтобы код подтверждения показывало непроверенное приложение.
Шаг 1. Устройство запрашивает код.
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"}'
{
"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 — сколько секунд у пользователя есть на подтверждение.
Этот шаг тоже ограничен по частоте — 30 запросов в минуту по адресу вызывающего, client_id в ключ не входит. Сверху действует ограничитель на входе в платформу, общий на весь /v1/. Превышение любого из них даёт 429 в общем конверте API с заголовком Retry-After; какой именно сработал, видно по наличию X-RateLimit-Limit.
Шаг 2. Пользователь открывает адрес и подтверждает. На экране подтверждения он видит карточку приложения, запрошенные права, выбор портала и предупреждение о фишинге: подтверждать следует только тот вход, который начал он сам.
Шаг 3. Устройство опрашивает /v1/connect/token.
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 |
Пользователь отклонил. Прекратить опрос |
access_denied с error_description=admin_approval_requested |
Пользователь попросил администратора портала разрешить приложению AI. Прекратить опрос, а после решения администратора запросить новый код с шага 1 |
expired_token |
Код истёк. Запросить новый с шага 1 |
Опрос может получить и 429 — это отказ ограничителя частоты, а не состояние авторизации, и форм у него две, потому что ограничителей на этом маршруте два. Отказ на входе в платформу приходит в форме RFC 6749 (error равен slow_down) и без заголовков X-RateLimit-*. Отказ ограничителя самого эндпоинта приходит в общем конверте API — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } } — и заголовки X-RateLimit-* несёт. Ветку по error === "slow_down" одной проверки поэтому недостаточно: она пропустит половину отказов.
Обрабатывать обе одинаково: ждать не меньше значения Retry-After и продолжать опрос, не сбрасывая свою растущую паузу. Отличить их, если нужно, можно по наличию X-RateLimit-Limit.
После подтверждения тот же запрос отдаёт 200 с ключом и двумя наборами прав:
{ "api_key": "vibe_api_...", "scopes": ["crm"], "granted_scopes": ["crm"] }
Обратите внимание: здесь объект уже, чем в основном потоке, — без блоков portal и user.
Ключ отдаётся один раз. Повторный опрос тем же device_code вернёт expired_token, поэтому сохраняйте ключ сразу.
Что обязано уметь устройство
- Показывать обратный отсчёт. Код живёт 15 минут; по истечении показать новый, а не мёртвый экран.
- Соблюдать
intervalи реагировать наslow_down. Опрос чаще разрешённого не ускорит выдачу, а приведёт к отказам. - Читать
Retry-Afterи наращивать собственную паузу. Значение заголовка — минимальная пауза, а не гарантия, что следующий запрос примут: за лимит по адресу конкурируют все запущенные копии приложения. Ждать не меньшеRetry-Afterи при этом увеличивать свою паузу с каждым отказом. - Не показывать
device_code. На экран идёт толькоuser_code.
Использование API-ключа
Полученный ключ имеет тип APP и работает как стандартный API-ключ Вайбкод. Передавайте его в заголовке X-Api-Key:
curl -H "X-Api-Key: vibe_api_..." \
https://vibecode.bitrix24.tech/v1/deals
Один и тот же ключ можно использовать с любым эндпоинтом Вайбкод API в пределах подтверждённых скоупов: список сделок, batch-запросы, агрегация и т. д.
Партнёрский ключ несёт ровно те права, которые пользователь подтвердил на странице согласия: платформенные скоупы сами к набору не добавляются. Нужен AI-роутер (/v1/chat/completions) — запросите vibe:ai вместе с остальными правами, как это сделать. Не запросили — вызов вернёт 403. Веб-поиск (/v1/search), vibe:infra и vibe:feedback выдаёт платформенный администратор, а vibe:storage приложению Connect не выдаётся, там же.
AI приложению на портале разрешает администратор портала — на странице согласия либо по запросу сотрудника. Если приложение запрашивает vibe:ai, подтверждает не администратор и разрешения у портала нет, страница согласия отвечает отказом CONNECT_AI_ADMIN_REQUIRED и предлагает попросить администратора, а после запроса на redirect_uri приходит error=access_denied с error_description=admin_approval_requested. Пока разрешения нет или администратор его отозвал, маршруты моделей (/v1/chat/completions, /v1/embeddings, /v1/models и другие) отвечают 403 с кодом CONNECT_AI_NOT_PERMITTED. Остальные права ключа при этом работают, перевыпускать ключ не нужно: как только администратор разрешит AI, тот же ключ снова получит доступ к моделям.
Партнёрский ключ работает с одним заголовком X-Api-Key и не требует Authorization: Bearer <session_token>. По префиксу его видно сразу: партнёрский ключ начинается с vibe_api_, а ключ OAuth-приложения из каталога Вайбкод — с vibe_app_, и для второго сессия пользователя обязательна.
Доступные скоупы
При вызове /v1/connect/authorize передавайте те же скоупы, что и при создании стандартного API-ключа в портале Битрикс24. На странице согласия пользователь видит список запрошенных прав и подтверждает его целиком — снять отдельный пункт нельзя, доступны только «Разрешить» и «Отклонить». Итоговый набор приходит в поле granted_scopes ответа /v1/connect/token, а запрошенный — в поле scopes рядом.
Актуальный перечень поддерживаемых значений описан в разделе Ключи и авторизация — он пополняется по мере появления новых модулей Битрикс24, поэтому здесь не дублируется.
Платформенные права Вайбкод делятся на три группы: vibe:ai приложение запрашивает само, vibe:search, vibe:infra и vibe:feedback выдаёт платформенный администратор, а vibe:storage приложению Connect не выдаётся. Порядок — Платформенные права.
Машиночитаемый перечень того, что приложение вправе запросить само, отдаёт поле scopes_supported документа /.well-known/oauth-authorization-server (RFC 8414). Права, выданные администратором конкретному приложению, в этот анонимный документ не попадают.
Время жизни и отзыв ключа
Код авторизации (
code) действует 5 минут и одноразовый. После обмена через/v1/connect/tokenповторный вызов с тем же кодом вернётinvalid_grant.Стейт согласия (внутреннее состояние страницы согласия) живёт 10 минут — если пользователь не подтвердит за это время, ссылку придётся выдавать заново.
API-ключ не имеет срока истечения. Действует, пока его не отзовут. Отзыв происходит одним из трёх способов:
- Пользователь отзывает доступ сам — раздел «Подключённые приложения» в его профиле Вайбкод. Это гасит все ключи, которые он выдал вашему приложению для этого портала, включая ключи от прежних авторизаций. Доступы других сотрудников того же портала не затрагиваются.
- Приложение отзывает свой ключ само —
POST /v1/connect/revoke, см. ниже. Гасится ровно один ключ, тот, который вы предъявили. - Платформенный администратор отзывает выданные клиентом ключи.
Деактивация клиента ключи НЕ отзывает — она только закрывает выдачу новых: с неактивным клиентом
/v1/connect/authorizeотклонит запрос, а уже выданные ключи продолжат работать.
При отзыве ключа все запросы с ним возвращают 401 KEY_INACTIVE. Если ключ удалён или неизвестен — 401 INVALID_API_KEY. Партнёр должен корректно обработать оба кода и предложить пользователю повторно авторизоваться.
Отзыв ключа приложением: `POST /v1/connect/revoke`
Ручка сделана по RFC 7009 и гасит один ключ — тот, который вы в ней предъявили. Вызывайте её, когда пользователь выключил интеграцию у вас на стороне: иначе прежние ключи остаются рабочими и занимают места в лимите (см. ниже).
POST /v1/connect/revoke
Content-Type: application/x-www-form-urlencoded
client_id=your-app&client_secret=your-secret&token=<api_key>
Принимается и JSON с теми же полями. Публичные клиенты (PUBLIC) client_secret не передают — его у них нет; конфиденциальные передают обязательно.
Ответы:
200 {}— либо ключ погашен, либо гасить было нечего. Один и тот же ответ на неизвестный токен, на чужой ключ и на уже отозванный — так вы не сможете перебором выяснять, живы ли чужие ключи. Значит, вызов идемпотентен: повторить его безопасно.400 invalid_request— не переданclient_idилиtoken.400 invalid_client— публичный клиент передалclient_secret.401 invalid_client— клиент неизвестен или секрет не подошёл.409 KEY_MANAGES_FOREIGN_SERVER— ключ управляет живым сервером другого владельца, сервером без владельца или сервером того же владельца на другом портале. Установите владельца и нужный портал сервера, перепривяжите его к ключу этого владельца в том же портале и затем повторите отзыв.409 KEY_OPERATION_IN_PROGRESS— тот же ключ сейчас меняет другая операция. Повторите запрос после её завершения. Тело обоих отказов содержитerror,error_descriptionиcode.429— превышена частота, и ограничителей здесь два. Собственный ограничитель ручки: 60 запросов в минуту. Точное значение — в заголовкеx-ratelimit-limit(потолок делится на реплики). Считается только по адресу вызывающего —client_idв ключ не входит намеренно, поэтому его ротация нового бюджета не даёт, а все копии интеграции за одним адресом делят эти 60; заголовкиX-RateLimit-*он ставит. Ограничитель на входе в платформу: 60 запросов в секунду на адрес (считает на входе, поэтому между репликами не делится), и это ведро общее на весь/v1/, а не на эту ручку; заголовковX-RateLimit-*он не ставит, аRetry-Afterу него всегда 1. Тело в обоих случаях приходит в общем конверте API —{ "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } }, — то есть отличается от формы RFC 7009 остальных ответов этой ручки, и различить ярусы по телу нельзя, только по наличиюX-RateLimit-Limit. Вызов не терминальный: ключ не отозван, повторить после паузы изRetry-After.
Отозвать можно и ключ, выданный до деактивации вашего клиента: деактивация закрывает выдачу новых ключей, но отзыв прежних не запрещает.
Чего ручка не делает:
- не снимает вебхук на портале Битрикс24 — его пользователь удаляет у себя, отзыв ключа этого не заменяет;
- не возвращает потерянный ключ — чтобы отозвать ключ, его надо предъявить, восстановить его нельзя;
- не срабатывает сама — если пользователь просто перестал пользоваться интеграцией, ключ живёт дальше; вызов ручки — ваша ответственность.
Если предъявленный ключ выпущен именно вашему клиенту и был активен, успешный отзыв прекращает его работу сразу: запросы с ним начинают отвечать 401 KEY_INACTIVE, а место в лимите освобождается. Сам по себе ответ 200 {} не доказывает отзыв: неизвестный, чужой клиенту и уже отозванный ключ дают тот же ответ.
Повторная авторизация выдаёт новый ключ
Каждое прохождение согласия выписывает новый API-ключ. Прежний ключ той же пары «пользователь и портал» при этом продолжает работать: авторизация не отзывает его. В профиле пользователя обе выдачи показаны одним подключением, и отзыв доступа гасит их вместе.
Что из этого следует для приложения:
- храните последний полученный ключ и заменяйте им предыдущий, иначе накопите рабочие ключи, о которых не помните,
- каждая повторная авторизация занимает место в лимите ключей — см. ниже,
- ключ, потерявший силу, восстановлению не подлежит: получите новый через
/v1/connect/authorize.
Лимит ключей
На одну пару «пользователь и портал» приходится не больше 100 действующих ключей — это и значение по умолчанию, и потолок настройки. Администратор портала может задать другое значение, от 1 до 100. В счёт идут все ключи пользователя на этом портале, а не только выданные вашим приложением.
Кроме пользовательского лимита есть портальный потолок платформы — он считается по всем живым ключам портала и не поднимается администратором портала. Когда один из пределов исчерпан, выдача отклоняется кодом KEY_LIMIT_REACHED: пользователь видит причину на странице согласия, а приложение получает только этот код в error_description при возврате на redirect_uri. Для пользовательского лимита место освобождает удаление ненужных ключей в разделе «API-ключи» кабинета Вайбкод или отзыв доступа приложению в профиле. При портальном потолке экран согласия предлагает выбрать тариф с большим лимитом ключей. Удаление ключей также освобождает место, если общее число действующих ключей портала становится ниже потолка.
Управление приложениями портала
Администратор выбранного портала видит приложения всех сотрудников в профиле на вкладке «Все приложения портала»: подключения, запросы к AI и долю лимита. В разделе «AI-модели» доступна разбивка «По приложениям».
«Разрешить ИИ» одобряет ожидающий запрос или выдаёт разрешение без запроса. Само разрешение не создаёт ключ: сотрудник повторяет подключение. «Запретить ИИ» закрывает вызовы моделей, сохраняя остальные права ключа. «Отключить для всех» отзывает все ключи приложения на этом портале и производные токены. Расход за текущий период остаётся в статистике.
Управление доступно, когда на платформе включено разрешение AI администратором портала. Ключи других порталов не затрагиваются.
Безопасность
- State-параметр — генерируйте криптостойкую случайную строку на каждый запрос и сверяйте при возврате на
redirect_uri. Защищает от CSRF. client_secret— только на сервере. Никогда не передавайте секрет в браузер, мобильное приложение или клиентский код. Обмен кода на ключ выполняется только серверным запросом.- Хранение ключа. API-ключ — секрет, который даёт доступ к данным пользователя. Храните в зашифрованном виде, ограничивайте доступ к записи подключения, не логируйте.
- Привязка
redirect_uri. Значение в/v1/connect/authorizeи/v1/connect/tokenдолжно совпадать символ в символ — несоответствие приведёт кinvalid_grant.
Регистрация партнёра
Клиент регистрируется самостоятельно в кабинете Вайбкод — раздел Connect-приложения, боковое меню, группа «Доступ». Войдите под учётной записью Вайбкод и нажмите «Зарегистрировать приложение» — client_id выдаётся сразу, без заявки и предварительной проверки.
В форме заполняются:
- название и краткое описание приложения,
- идентификатор — строчные латинские буквы, цифры и дефис,
- один или несколько
redirect_uri— адресаhttps://либо локальные127.0.0.1,localhostи[::1]поhttpилиhttps, - запрашиваемые права доступа.
Набор прав не может быть пустым: регистрация без единого права возвращает EMPTY_SCOPES. Приложение без прав не может запросить у пользователя ничего.
Тип клиента выбирается при создании и позже не меняется — разбор с примерами в разделе Типы клиента.
client_secret показывается один раз при создании. Прочитать его повторно нельзя, только перевыпустить действием «Обновить секрет».
В ⋮-меню карточки приложения есть два помощника: «Собрать ссылку» — конструктор ссылки авторизации (выбор redirect_uri и прав, готовый URL и curl для обмена кода, для клиента без секрета — одноразовая пара строк PKCE), и «Как увидит клиент» — превью страницы согласия ровно в том виде, в каком её увидит пользователь, включая бейдж верификации.
Число активных клиентов на одного пользователя ограничено. При достижении предела регистрация возвращает CLIENT_LIMIT_REACHED, текущее значение приходит в поле limit. Деактивация клиента освобождает место.
Новое приложение получает статус «непроверенное» — пользователь видит эту отметку на странице согласия. Повысить статус может платформенный администратор, запрос отправляется действием «Запросить верификацию» в карточке приложения. Заявку могут отклонить с причиной — она приходит владельцу приложения, и после правок заявку подают заново.
Платформенные права
Права Вайбкод открывают разделы самой платформы, а не данные Битрикс24. Для приложения Connect они делятся на три группы.
vibe:ai приложение запрашивает само. Отметьте его в правах при регистрации — там же, где права Битрикс24, — и дальше оно идёт обычным путём: передаётся в параметре scope у /v1/connect/authorize, показывается пользователю на странице согласия строкой «AI-модели» и приходит на выданный ключ. Заявки и проверки для этого не нужно. Расход покрывает месячный AI-лимит аккаунта, к которому подключается приложение.
Автоматически право не добавляется: приложение, которое его не отметило, работает без доступа к AI-роутеру, а уже выданные ключи набор прав не меняют. Чтобы открыть AI существующему подключению, отметьте право в карточке приложения и проведите пользователя через согласие заново — он получит новый ключ.
vibe:search, vibe:infra и vibe:feedback выдаёт платформенный администратор. При саморегистрации запрос с ними отклоняется кодом SCOPE_NOT_ALLOWED. Порядок — три шага, и первые два делает владелец приложения:
- Отправьте заявку действием «Запросить верификацию» в карточке приложения.
- Напишите в ленте «Обсуждение с платформой» той же карточки, какое право нужно и для какого сценария. Отдельного поля «запрашиваю право» в заявке нет, поэтому без сообщения проверяющий видит только заявку на значок.
- Администратор выдаёт право проверенному приложению. Верификация сама по себе прав не открывает — это два разных решения, и первое является условием второго.
После выдачи право ведёт себя как vibe:ai: передаётся в scope, показывается на странице согласия, приходит на ключ.
vibe:storage приложению Connect не выдаётся. Ключ, выданный через Connect, попадает в ту же область хранилища, что и личные ключи подключившего пользователя в этом аккаунте Битрикс24. С этим правом приложение видело бы все файлы, загруженные его личными ключами. Поэтому право не выдаёт даже платформенный администратор, а запрос с ним при саморегистрации отклоняется кодом SCOPE_NOT_ALLOWED.
Что снимает отметку «проверено»
Отметка держится на том, каким приложение прошло проверку, поэтому правка четырёх полей возвращает статус «непроверенное»:
- название,
- логотип,
- адреса возврата
redirect_uri, - набор запрашиваемых прав.
Описание и адрес сайта на отметку не влияют.
Кабинет предупреждает до сохранения: форма показывает подтверждение с перечнем полей, из-за которых отметка будет снята. Без подтверждения правка не сохраняется, ответ — VERIFICATION_RESET_NOT_CONFIRMED с тем же перечнем в теле.
Заполнение пустого набора прав отметку не снимает, когда выбранные права укладываются в набор, выданный администратором.
Сброс касается только отметки. Выданные ключи, подключённые пользователи, платформенные права и разрешённый вход по коду устройства сохраняются. Заявку на проверку подают заново.
Типы клиента
Тип отвечает на один вопрос: есть ли у приложения место, недоступное пользователю, где можно хранить секрет. От ответа зависит, выдаётся ли client_secret.
Конфиденциальный
У приложения есть серверная часть. client_secret лежит там и никогда не попадает в браузер или на устройство пользователя.
Примеры: SaaS-сервис аналитики, который ходит в портал клиента по расписанию. Синхронизация с 1С на вашем сервере. Чат-ассистент, который принимает вебхуки и отвечает от своего бэкенда.
Этот тип нужен для потока, описанного на этой странице: обмен кода на ключ выполняется серверным запросом с client_secret.
Публичный
Секрет спрятать негде — код целиком выполняется на устройстве пользователя. client_secret не выдаётся, вместо него подлинность подтверждает PKCE: приложение генерирует на каждый запуск потока случайную строку, отправляет в /v1/connect/authorize её хеш, а в /v1/connect/token — саму строку. Перехваченный код без неё не обменивается.
Примеры: мобильное приложение. Десктопная программа. Одностраничное веб-приложение без собственного бэкенда.
Как выбрать
Если у приложения есть сервер, который вы контролируете, — конфиденциальный. Если код целиком уезжает пользователю — публичный.
Тип задаётся один раз при регистрации. Чтобы сменить его, зарегистрируйте нового клиента.