Для AI-агентов: markdown этой страницы — /docs-content/search/research.md индекс документации — /llms.txt
Глубокий поиск (research)
POST /v1/research
Запускает глубокий итеративный поиск с многошаговым агентным циклом «искать → читать → анализировать». Возвращает структурированный отчёт с разделами, источниками и опциональными уточняющими вопросами. Эндпоинт всегда работает в потоковом режиме через Server-Sent Events — один запрос длится 10–60 секунд и не помещается в типичный таймаут синхронного HTTP.
Поля запроса (body)
| Поле | Тип | Обяз. | По умолч. | Описание |
|---|---|---|---|---|
Полеquery |
Типstring | Обяз.да | По умолч.— | ОписаниеТекст исследовательского запроса. От 1 до 2000 символов |
Полеprovider |
Типstring | Обяз.нет | По умолч.research-движок по умолчанию инстанса | ОписаниеПровайдер с поддержкой research: tavily, exa, you-com, linkup, perplexity или jina. Платформенный bitrix-search поддерживает research в зависимости от инстанса. Актуальный список — в GET /v1/search/providers с capabilities.modes.research: true |
ПолеmaxSteps |
Типnumber | Обяз.нет | По умолч.5 | ОписаниеМаксимум шагов агентного цикла. От 1 до 10. Адаптер ограничивает значение, если внешний провайдер не принимает столько шагов |
Полеlang |
Типstring | Обяз.нет | По умолч.auto |
ОписаниеЯзык исследования: ru, en или auto |
ПолеincludeDomains |
Типstring[] | Обяз.нет | По умолч.[] |
ОписаниеПоиск только по доменам из списка. До 50 имён хоста. Отдельные движки берут не больше 20 — при превышении список обрезается, а в кадре done появляется ignored_filters с includeDomains_truncated |
ПолеexcludeDomains |
Типstring[] | Обяз.нет | По умолч.[] |
ОписаниеИсключить домены. До 50 имён хоста. Отдельные движки берут не больше 20 — при превышении список обрезается, а в кадре done появляется ignored_filters с excludeDomains_truncated |
Примеры
curl — личный ключ
curl -N -X POST https://vibecode.bitrix24.tech/v1/research \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Сравни возможности Cursor 3 и Windsurf для AI-кодинга",
"provider": "tavily",
"maxSteps": 5,
"lang": "ru"
}'
curl — OAuth-приложение
curl -N -X POST https://vibecode.bitrix24.tech/v1/research \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "Сравни возможности Cursor 3 и Windsurf для AI-кодинга",
"provider": "tavily",
"maxSteps": 5,
"lang": "ru"
}'
JavaScript — личный ключ
const res = await fetch('https://vibecode.bitrix24.tech/v1/research', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'Сравни возможности Cursor 3 и Windsurf для AI-кодинга',
provider: 'tavily',
maxSteps: 5,
lang: 'ru',
}),
})
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const events = buffer.split('\n\n')
buffer = events.pop() ?? ''
for (const block of events) {
const eventLine = block.split('\n').find(l => l.startsWith('event: '))
const dataLine = block.split('\n').find(l => l.startsWith('data: '))
if (!eventLine || !dataLine) continue
const event = eventLine.slice(7)
const data = JSON.parse(dataLine.slice(6))
if (event === 'done') {
console.log('Готово:', data.synthesized_answer.slice(0, 200))
console.log('Источников:', data.sources.length)
}
}
}
JavaScript — OAuth-приложение
const res = await fetch('https://vibecode.bitrix24.tech/v1/research', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'Сравни возможности Cursor 3 и Windsurf для AI-кодинга',
provider: 'tavily',
maxSteps: 5,
lang: 'ru',
}),
})
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const events = buffer.split('\n\n')
buffer = events.pop() ?? ''
for (const block of events) {
const eventLine = block.split('\n').find(l => l.startsWith('event: '))
const dataLine = block.split('\n').find(l => l.startsWith('data: '))
if (!eventLine || !dataLine) continue
const event = eventLine.slice(7)
const data = JSON.parse(dataLine.slice(6))
if (event === 'done') {
console.log('Готово:', data.synthesized_answer.slice(0, 200))
console.log('Источников:', data.sources.length)
}
}
}
Поля ответа
Ответ приходит как поток text/event-stream. Каждое событие — пара event: <type> + data: <JSON>, разделённая пустой строкой. Поток гарантированно завершается одним из двух событий: done (успех) или error (сбой).
События потока
| Событие | Когда возникает | Поля data |
|---|---|---|
Событиеstart |
Когда возникаетВ начале запроса | Поля dataresearch_id, provider |
Событиеthinking |
Когда возникаетПромежуточные размышления агента | Поля datacontent — текстовый фрагмент |
Событиеtool_call |
Когда возникаетАгент вызвал внутренний инструмент | Поля datatool (web_search / content_extraction / external_tool), description |
Событиеtool_result |
Когда возникаетИнструмент вернул результат | Поля datadescription, items_count |
Событиеsection_complete |
Когда возникаетЗавершён раздел исследования с привязанными источниками | Поля datasection.title, section.content, section.sourceIds |
Событиеanswer_delta |
Когда возникаетОчередной фрагмент финального ответа | Поля datacontent — кусок текста |
Событиеdone |
Когда возникаетФинальный блок | Поля dataсм. таблицу «Поля события done» ниже |
Событиеerror |
Когда возникаетСбой во время обработки | Поля dataerror.code, error.message |
Какие именно промежуточные события придут — зависит от провайдера. Ни одно из них не обязательно: получение start и done достаточно для штатного завершения.
Поля события `done`
| Поле | Тип | Описание |
|---|---|---|
Полеquery |
Типstring | ОписаниеТекст исходного запроса |
Полеprovider |
Типstring | ОписаниеИдентификатор провайдера, который обработал запрос (например, tavily, exa) |
Полеsynthesized_answer |
Типstring | ОписаниеПолный синтезированный ответ исследования |
Полеoutline |
Типobject | null | ОписаниеСтруктура разделов отчёта. null, если провайдер не возвращает разбивку |
Полеoutline.sections |
Типarray | ОписаниеМассив разделов |
Полеoutline.sections[].title |
Типstring | ОписаниеЗаголовок раздела |
Полеoutline.sections[].content |
Типstring | ОписаниеТекст раздела |
Полеoutline.sections[].sourceIds |
Типnumber[] | ОписаниеИдентификаторы источников из sources[], на которые опирается раздел |
Полеsources |
Типarray | ОписаниеМассив источников исследования |
Полеsources[].id |
Типnumber | ОписаниеПорядковый номер источника |
Полеsources[].url |
Типstring | ОписаниеАдрес страницы |
Полеsources[].title |
Типstring | ОписаниеЗаголовок страницы |
Полеsources[].fullContent |
Типstring | ОписаниеПолный текст страницы. Длина ограничена провайдером |
Полеsources[].publishedDate |
Типstring | null | ОписаниеДата публикации в формате ISO 8601 |
Полеfollow_up_questions |
Типstring[] | null | ОписаниеУточняющие вопросы для углубления исследования. null, если провайдер не предоставляет |
Полеreasoning |
Типarray | null | ОписаниеШаги рассуждения агента. null, если провайдер не предоставляет |
Полеreasoning[].step |
Типnumber | ОписаниеПорядковый номер шага |
Полеreasoning[].description |
Типstring | ОписаниеКраткое описание шага |
Полеresearch_id |
Типstring | ОписаниеИдентификатор запроса в формате wr_<14 цифр>_<8 hex> |
Полеupstream_search_id |
Типstring | null | ОписаниеИдентификатор у внешнего провайдера. Полезен для разбора инцидентов |
Полеbilling_source |
Типstring | ОписаниеНеобязательное. Источник оплаты: wallet — кошелёк, включая оплату сверх квоты ИИ, quota — квота ИИ портала, cowork — квота места Cowork, byok — собственный ключ провайдера. Для бесплатного запроса через платформенный ключ поле отсутствует |
Полеcost_vibes |
Типnumber | ОписаниеСколько Ꝟ фактически списано с кошелька. При billing_source, равном quota, cowork или byok, значение равно 0 |
Полеduration_ms |
Типnumber | ОписаниеДлительность обработки запроса в миллисекундах |
Полеpartial_charge |
Типboolean | ОписаниеПрисутствует, когда параллельные запросы исчерпали остаток баланса до конца текущего списания. Запрос завершился успешно, фактически списано меньше номинальной стоимости — итог в cost_vibes |
Полеcharge_log_failed |
Типboolean | ОписаниеНеобязательное. true, если произошёл сбой списания или записи журнала использования. Успешный результат исследования возвращается клиенту. Если списание состоялось до сбоя журнала, cost_vibes сохраняет фактически списанную с кошелька сумму |
Полеignored_filters |
Типstring[] | ОписаниеНеобязательное. Переданные фильтры, которые движок не смог применить. Например, includeDomains_truncated, когда передано больше 20 доменов и список обрезан до 20 |
Пример ответа
event: start
data: {"research_id":"wr_20260504112304_a1b2c3d4","provider":"tavily"}
event: tool_call
data: {"tool":"web_search","description":"Web research started"}
event: tool_result
data: {"description":"Web research completed in 18s","items_count":8}
event: done
data: {"query":"Сравни возможности Cursor 3 и Windsurf для AI-кодинга","provider":"tavily","synthesized_answer":"Cursor 3 — это переработанная среда для AI-разработки [1][2]. Windsurf, в свою очередь, делает упор на agentic-flow [3]…","outline":null,"sources":[{"id":1,"url":"https://cursor.com/blog/cursor-3","title":"Meet the new Cursor","fullContent":"…","publishedDate":"2026-04-15T00:00:00Z"}],"follow_up_questions":null,"reasoning":null,"ignored_filters":["lang"],"research_id":"wr_20260504112304_a1b2c3d4","upstream_search_id":"tvly-research-abc","billing_source":"byok","cost_vibes":0,"duration_ms":18230}
Пример ответа при ошибке
402 — на балансе не хватает Ꝟ:
{
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient vibes for this research request. Top up your account to continue.",
"userMessage": "Недостаточно vibes — пополните баланс или добавьте свой BYOK-ключ (см. GET /v1/search/providers).",
"hint": "Add a BYOK key to search for free — see GET /v1/search/providers",
"required": 25,
"topupUrl": "https://vibecode.bitrix24.tech/r/upgrade?c=ai_quota_copay&sc=api&m=api&t=topup&p=…&e=…&s=…"
}
}
Четыре поля приходят внутри объекта error, а не на верхнем уровне ответа: userMessage — текст на языке пользователя, hint — подсказка по дальнейшим действиям, required — сколько Ꝟ нужно на этот запрос, topupUrl — ссылка на пополнение баланса. Набор общий для INSUFFICIENT_BALANCE и BILLING_FROZEN. Остальные ситуации — в таблице ошибок ниже.
Ошибки
Ошибки до открытия SSE-потока возвращаются обычным JSON-ответом. Ошибки, возникшие после установки соединения, приходят как событие error в потоке.
| HTTP | Код | Описание |
|---|---|---|
| HTTP400 | КодINVALID_REQUEST |
ОписаниеПустой query, query длиннее 2000 символов, maxSteps вне диапазона 1..10, provider не из списка поддерживаемых провайдеров, includeDomains или excludeDomains длиннее 50 элементов |
| HTTP402 | КодINSUFFICIENT_BALANCE |
ОписаниеНа балансе портала недостаточно Ꝟ для платного провайдера. Перейдите на BYOK или пополните баланс |
| HTTP402 | КодBILLING_FROZEN |
ОписаниеБиллинг-аккаунт заморожен |
| HTTP402 | КодAI_QUOTA_EXHAUSTED |
ОписаниеКвота ИИ исчерпана, оплата сверх квоты недоступна. Причина — в error.reason, время сброса — в error.resetAt. Дождитесь сброса, разрешите перерасход в кабинете и пополните кошелёк или используйте свой ключ провайдера. Необязательное поле error.learnMore содержит дополнительные сведения |
| HTTP429 | КодAI_PACING_LIMITED |
ОписаниеДостигнут лимит равномерного расходования квоты ИИ. Ответ содержит error.reason, error.overageDenied, error.resetAt и заголовок Retry-After в секундах. Дождитесь указанного времени или используйте свой ключ провайдера |
| HTTP402 | КодCOWORK_QUOTA_EXHAUSTED |
ОписаниеИсчерпано окно квоты места Cowork. Поле error.window принимает fiveHour, week или month. Дождитесь error.resetAt или задержки из заголовка Retry-After в секундах, либо используйте свой ключ провайдера |
| HTTP402 | КодCOWORK_SUBSCRIPTION_INACTIVE |
ОписаниеНет активного места Cowork. Активируйте или возобновите место, либо используйте свой ключ провайдера |
| HTTP402 | Кодcompany_budget_exhausted |
ОписаниеИсчерпан месячный бюджет расхода компании, который задаёт администратор портала. Поле scope называет пробитый бюджет: USER — личный бюджет вызывающего, PORTAL — бюджет всего портала. Поле canRequest говорит, можно ли запросить увеличение: у личного бюджета true, у портального false — его поднимает только администратор. Отказ приходит при оплате из кошелька, включая оплату сверх квоты ИИ. Исследование на своём BYOK-ключе бесплатно и продолжает работать |
| HTTP403 | КодSCOPE_DENIED |
ОписаниеКлючу не хватает скоупа vibe:search |
| HTTP404 | КодPROVIDER_NOT_FOUND |
ОписаниеПровайдер с таким идентификатором отсутствует или недоступен |
| HTTP404 | КодCREDENTIAL_NOT_FOUND |
ОписаниеДля провайдера нет ни USER, ни PORTAL, ни PLATFORM-ключа |
| HTTP404 | КодPROVIDER_DOES_NOT_SUPPORT_RESEARCH |
ОписаниеЗапрошен провайдер без поддержки research: brave, z-ai или bitrix-search, когда у движка инстанса нет режима research. Такие провайдеры доступны только в POST /v1/search |
| HTTP429 | КодRATE_LIMITED |
ОписаниеПревышен лимит 20 запросов в минуту на портал — общий для всех его API-ключей. Действующее значение — в заголовке x-ratelimit-limit |
| HTTP429 | КодSEARCH_DAILY_CAP_REACHED |
ОписаниеС этим запросом суммарный расход поиска из кошелька, квоты ИИ и квоты места Cowork за текущие сутки по московскому времени превысил бы потолок, который задал администратор портала. Отказ приходит обычным JSON-ответом до открытия потока, провайдер не вызывается, списания нет. Потолок общий для /v1/research и POST /v1/search. Ответ несёт заголовок Retry-After, а тело ошибки — поля resetAt с началом следующих суток по московскому времени и hint. Исследование на своём BYOK-ключе потолок не ограничивает |
| HTTP503 | КодFEATURE_NOT_ENABLED |
ОписаниеГлубокое исследование недоступно на этой платформе |
Ошибки внутри SSE-потока:
| Код | Описание |
|---|---|
КодUPSTREAM_ERROR |
ОписаниеПровайдер вернул ошибку во время обработки. Списания нет |
КодPROVIDER_DOES_NOT_SUPPORT_RESEARCH |
ОписаниеЗащитная проверка адаптера на стороне сервера — фактически дубль HTTP-кода 404, всплывает при рассинхронизации каталога |
КодINTERNAL_ERROR |
ОписаниеВнутренняя ошибка сервера во время обработки |
Полный список общих ошибок API — Ошибки.
Известные особенности
Только потоковый режим. Синхронного варианта /v1/research без SSE не существует — глубокое исследование длится 10–60 секунд и не вписывается в обычные HTTP-таймауты прокси. Если клиент не может работать с потоком, накапливайте события на сервере-посреднике и отдавайте их пакетом конечному приложению.
Лимит 20 запросов в минуту на портал. Действующее значение — в заголовке x-ratelimit-limit. Ниже, чем у POST /v1/search, потому что один research-запрос потребляет ресурсы провайдера в десятки раз дольше обычного поиска. Лимит общий для всех API-ключей портала — они делят его между собой, поэтому распределение нагрузки по нескольким ключам предел не поднимает. При превышении возвращается 429 RATE_LIMITED.
Тариф платформенного research-движка зависит от инстанса. Сколько Ꝟ стоит режим research у платформенного движка, возвращает GET /v1/search/providers в поле pricing.research. Для всех BYOK-провайдеров стоимость на стороне платформы — 0 Ꝟ, оплата идёт напрямую с вашего аккаунта у поставщика.
Каскад выбора ключа отличается от /v1/search. В POST /v1/search при опущенном provider платформа сначала проверяет USER/PORTAL-дефолт по любому провайдеру и только потом уходит в движок по умолчанию инстанса. В research всё иначе: если поле provider опущено, сервер сразу подставляет research-движок по умолчанию, настроенный на инстансе, не проверяя USER/PORTAL-дефолты по другим провайдерам. Если для этого движка нет ни одного ключа, возвращается 404 CREDENTIAL_NOT_FOUND — даже если у пользователя есть дефолтный ключ для exa или другого research-провайдера. Каскад USER → PORTAL → PLATFORM применяется уже внутри выбранного провайдера. Чтобы пойти через другой research-провайдер, передавайте provider явно.
Списание происходит только после успешного завершения потока. До обращения к провайдеру проверяется возможность оплаты из выбранного источника. Для оплаты из кошелька, включая перерасход квоты ИИ, проверяются баланс и бюджет компании. При обрыве потока с ошибкой провайдера квота и кошелёк не расходуются. Порядок выбора источника оплаты — в разделе Тарификация.