For AI agents: markdown of this page — /docs-content-en/search/research.md documentation index — /llms.txt

Deep research

POST /v1/research

Launches a deep iterative search with a multi-step agentic loop "search → read → analyze". Returns a structured report with sections, sources, and optional follow-up questions. The endpoint always works in streaming mode via Server-Sent Events — a single request lasts 10–60 seconds and does not fit into a typical synchronous HTTP timeout.

Request fields (body)

Field Type Req. Default Description
query string yes Research query text. From 1 to 2000 characters
provider string no instance default research engine A provider with research support: tavily, exa, you-com, linkup, perplexity, or jina. The platform-managed bitrix-search supports research depending on the instance. For the current list, see GET /v1/search/providers — the providers with capabilities.modes.research: true
maxSteps number no 5 Maximum number of agentic-loop steps. From 1 to 10. The adapter caps the value if the external provider does not accept that many steps
lang string no auto Research language: ru, en, or auto
includeDomains string[] no [] Search only within the listed domains. Up to 50 host names. Some engines take no more than 20 — on overflow the list is truncated, and the done frame carries ignored_filters with includeDomains_truncated
excludeDomains string[] no [] Exclude domains. Up to 50 host names. Some engines take no more than 20 — on overflow the list is truncated, and the done frame carries ignored_filters with excludeDomains_truncated

Examples

curl — personal key

Terminal
curl -N -X POST https://vibecode.bitrix24.com/v1/research \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "Compare the capabilities of Cursor 3 and Windsurf for AI coding",
    "provider": "tavily",
    "maxSteps": 5,
    "lang": "en"
  }'

curl — OAuth app

Terminal
curl -N -X POST https://vibecode.bitrix24.com/v1/research \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "Compare the capabilities of Cursor 3 and Windsurf for AI coding",
    "provider": "tavily",
    "maxSteps": 5,
    "lang": "en"
  }'

JavaScript — personal key

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/research', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: 'Compare the capabilities of Cursor 3 and Windsurf for AI coding',
    provider: 'tavily',
    maxSteps: 5,
    lang: 'en',
  }),
})

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('Done:', data.synthesized_answer.slice(0, 200))
      console.log('Sources:', data.sources.length)
    }
  }
}

JavaScript — OAuth app

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/research', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: 'Compare the capabilities of Cursor 3 and Windsurf for AI coding',
    provider: 'tavily',
    maxSteps: 5,
    lang: 'en',
  }),
})

const reader = res.body.getReader()

Event stream (SSE)

The response arrives as a text/event-stream. Each event is a pair event: <type> + data: <JSON>, separated by a blank line. The stream is guaranteed to end with one of two events: done (success) or error (failure).

Event When it occurs data fields
start At the start of the request research_id, provider
thinking Intermediate agent reasoning content — a text fragment
tool_call The agent invoked an internal tool tool (web_search / content_extraction / external_tool), description
tool_result A tool returned a result description, items_count
section_complete A research section is finished, with its sources attached section.title, section.content, section.sourceIds
answer_delta The next fragment of the final answer content — a chunk of text
done Final block see the "Fields of the done event" table below
error Failure during processing error.code, error.message

Which intermediate events arrive depends on the provider. None of them is mandatory: receiving start and done is enough for a normal completion.

Fields of the `done` event

Field Type Description
query string The original query text
provider string Identifier of the provider that processed the request (for example, tavily, exa)
synthesized_answer string The full synthesized research answer
outline object | null The structure of the report's sections. null if the provider does not return a breakdown
outline.sections array Array of sections
outline.sections[].title string Section title
outline.sections[].content string Section text
outline.sections[].sourceIds number[] Identifiers of the sources from sources[] that the section relies on
sources array Array of research sources
sources[].id number Sequential source number
sources[].url string Page URL
sources[].title string Page title
sources[].fullContent string Full page text. Length is capped by the provider
sources[].publishedDate string | null Publication date in ISO 8601 format
follow_up_questions string[] | null Follow-up questions for deepening the research. null if the provider does not return them
reasoning array | null The agent's reasoning steps. null if the provider does not return them
reasoning[].step number Sequential step number
reasoning[].description string Short step description
research_id string Request identifier in the format wr_<14 digits>_<8 hex>
upstream_search_id string | null Identifier at the external provider. Useful for incident investigation
cost_vibes number How many Ꝟ were actually charged
duration_ms number Request processing duration in milliseconds
partial_charge boolean Present when parallel requests exhausted the remaining balance before the end of the current charge. The request completed successfully, but less than the nominal cost was actually charged — the total is in cost_vibes
charge_log_failed boolean Present when the usage log could not be written. The result is returned, but no funds are charged (cost_vibes: 0)
ignored_filters string[] Optional. The filters that were passed but the engine could not apply. For example, includeDomains_truncated when more than 20 domains were passed and the list was truncated to 20

Event-stream example

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":"got 8 sources","items_count":8}

event: tool_call
data: {"tool":"content_extraction","description":"reading top sources"}

event: answer_delta
data: {"content":"Cursor 3 is a reworked environment "}

event: answer_delta
data: {"content":"for AI development [1][2]. Windsurf, in turn, "}

event: done
data: {"query":"Compare the capabilities of Cursor 3 and Windsurf for AI coding","provider":"tavily","synthesized_answer":"Cursor 3 is a reworked environment for AI development [1][2]. Windsurf, in turn, focuses on 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,"research_id":"wr_20260504112304_a1b2c3d4","upstream_search_id":"tvly-research-abc","cost_vibes":0,"duration_ms":18230}

Error response example

402 — not enough Ꝟ on the balance:

JSON
{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Insufficient vibes for this research request.",
    "userMessage": "Insufficient Vibe credits — top up your balance or add your own BYOK key (see GET /v1/search/providers).",
    "hint": "Add a BYOK key to search for free — see GET /v1/search/providers",
    "required": 25
  }
}

Three fields arrive inside the error object, not at the top level of the response: userMessage — text in the user's language, hint — guidance on next steps, required — how many Ꝟ this request needs. The same set is used for INSUFFICIENT_BALANCE and BILLING_FROZEN. All other cases are listed in the error table below.

Errors

Errors before the SSE stream opens are returned as a regular JSON response. Errors that occur after the connection is established arrive as an error event in the stream.

HTTP Code Description
400 INVALID_REQUEST Empty query, query longer than 2000 characters, maxSteps outside the 1..10 range, provider not from the list of supported providers, includeDomains or excludeDomains with more than 50 elements
402 INSUFFICIENT_BALANCE The portal balance has insufficient Ꝟ for a paid provider. Switch to BYOK or top up the balance
402 BILLING_FROZEN The billing account is frozen
403 SCOPE_DENIED The key lacks the vibe:search scope
404 PROVIDER_NOT_FOUND A provider with this identifier does not exist or is unavailable
404 CREDENTIAL_NOT_FOUND There is no USER, PORTAL, or PLATFORM key for the provider
404 PROVIDER_DOES_NOT_SUPPORT_RESEARCH A provider without research support was requested: brave, z-ai, or bitrix-search when the instance engine has no research mode. Such providers are available only in POST /v1/search
429 RATE_LIMITED The limit of 20 requests per minute per portal — shared by all of its API keys — was exceeded
503 FEATURE_NOT_ENABLED Deep research is unavailable on this platform

Errors inside the SSE stream:

Code Description
UPSTREAM_ERROR The provider returned an error during processing. No charge
PROVIDER_DOES_NOT_SUPPORT_RESEARCH A server-side adapter guard — effectively a duplicate of the HTTP 404 code, surfacing when the catalog is out of sync
INTERNAL_ERROR Internal server error during processing

The full list of common API errors — Errors.

Known specifics

Streaming mode only. There is no synchronous variant of /v1/research without SSE — deep research lasts 10–60 seconds and does not fit into ordinary HTTP proxy timeouts. If a client cannot work with a stream, accumulate events on an intermediary server and deliver them in a batch to the end application.

Limit of 20 requests per minute per Bitrix24 account. Lower than for POST /v1/search, because a single research request occupies the provider's resources dozens of times longer than an ordinary search. The limit is shared by all API keys of the Bitrix24 account — they draw on a single counter, so spreading the load across several keys does not raise the ceiling. When the limit is exceeded, 429 RATE_LIMITED is returned.

The platform research engine's pricing depends on the instance. GET /v1/search/providers returns the cost of the research mode for the platform engine in the pricing.research field. For all BYOK providers the cost on the platform side is 0 Ꝟ — you are billed directly by the provider, on your own account with them.

The key selection cascade differs from /v1/search. In POST /v1/search, when provider is omitted, the platform first checks the USER/PORTAL default for any provider and only then falls back to the instance default engine. In research it is different: if the provider field is omitted, the server immediately substitutes the default research engine configured on the instance, without checking USER/PORTAL defaults for other providers. If that engine has no key at all, 404 CREDENTIAL_NOT_FOUND is returned — even if the user has a default key for exa or another research provider. The USER → PORTAL → PLATFORM cascade applies within the chosen provider. To go through a different research provider, pass provider explicitly.

Charging happens only after the stream completes successfully. The balance is checked before the run starts: if it is insufficient, 402 INSUFFICIENT_BALANCE is returned without contacting the provider. If the stream breaks with a provider error, the balance does not change — there is nothing to charge for.

See also