لوكلاء AI: ملف markdown لهذه الصفحة — /docs-content-en/search/research.md فهرس التوثيق — /llms.txt

مقالات التوثيق متاحة حاليًا باللغة الإنجليزية فقط.

Deep research

POST /v1/research

Launches a deep iterative search driven by 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 Required Default Description
Fieldquery Typestring Requiredyes Default— DescriptionResearch query text. From 1 to 2000 characters
Fieldprovider Typestring Requiredno Defaultinstance default research engine DescriptionA 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
FieldmaxSteps Typenumber Requiredno Default5 DescriptionMaximum number of agentic-loop steps. From 1 to 10. The adapter caps the value if the external provider does not accept that many steps
Fieldlang Typestring Requiredno Defaultauto DescriptionResearch language: ru, en, or auto
FieldincludeDomains Typestring[] Requiredno Default[] DescriptionSearch 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
FieldexcludeDomains Typestring[] Requiredno Default[] DescriptionExclude 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 application

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 application

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()
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)
    }
  }
}

Response fields

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).

Stream events

Event When it occurs data fields
Eventstart When it occursAt the start of the request data fieldsresearch_id, provider
Eventthinking When it occursIntermediate agent reasoning data fieldscontent — a text fragment
Eventtool_call When it occursThe agent invoked an internal tool data fieldstool (web_search / content_extraction / external_tool), description
Eventtool_result When it occursA tool returned a result data fieldsdescription, items_count
Eventsection_complete When it occursA research section is finished, with its sources attached data fieldssection.title, section.content, section.sourceIds
Eventanswer_delta When it occursThe next fragment of the final answer data fieldscontent — a chunk of text
Eventdone When it occursFinal block data fieldssee the "Fields of the done event" table below
Eventerror When it occursFailure during processing data fieldserror.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
Fieldquery Typestring DescriptionThe original query text
Fieldprovider Typestring DescriptionIdentifier of the provider that processed the request (for example, tavily, exa)
Fieldsynthesized_answer Typestring DescriptionThe full synthesized research answer
Fieldoutline Typeobject | null DescriptionThe structure of the report's sections. null if the provider does not return a breakdown
Fieldoutline.sections Typearray DescriptionArray of sections
Fieldoutline.sections[].title Typestring DescriptionSection title
Fieldoutline.sections[].content Typestring DescriptionSection text
Fieldoutline.sections[].sourceIds Typenumber[] DescriptionIdentifiers of the sources from sources[] that the section relies on
Fieldsources Typearray DescriptionArray of research sources
Fieldsources[].id Typenumber DescriptionSequential source number
Fieldsources[].url Typestring DescriptionPage URL
Fieldsources[].title Typestring DescriptionPage title
Fieldsources[].fullContent Typestring DescriptionFull page text. Length is capped by the provider
Fieldsources[].publishedDate Typestring | null DescriptionPublication date in ISO 8601 format
Fieldfollow_up_questions Typestring[] | null DescriptionFollow-up questions for deepening the research. null if the provider does not return them
Fieldreasoning Typearray | null DescriptionThe agent's reasoning steps. null if the provider does not return them
Fieldreasoning[].step Typenumber DescriptionSequential step number
Fieldreasoning[].description Typestring DescriptionShort step description
Fieldresearch_id Typestring DescriptionRequest identifier in the format wr_<14 digits>_<8 hex>
Fieldupstream_search_id Typestring | null DescriptionIdentifier at the external provider. Useful for incident investigation
Fieldcost_vibes Typenumber DescriptionHow many Ꝟ were actually charged
Fieldduration_ms Typenumber DescriptionRequest processing duration in milliseconds
Fieldpartial_charge Typeboolean DescriptionPresent 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
Fieldcharge_log_failed Typeboolean DescriptionPresent when the usage log could not be written. The result is returned, but no funds are charged (cost_vibes: 0)
Fieldignored_filters Typestring[] DescriptionOptional. The filters that were passed but that the engine could not apply. For example, includeDomains_truncated when more than 20 domains were passed and the list was truncated to 20

Response 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":"Web research completed in 18s","items_count":8}

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,"ignored_filters":["lang"],"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. Top up your account to continue.",
    "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,
    "topupUrl": "https://vibecode.bitrix24.com/r/upgrade?c=ai_quota_copay&sc=api&m=api&t=topup&p=…&e=…&s=…"
  }
}

Four 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, topupUrl — a link to top up the balance. The same set is used for INSUFFICIENT_BALANCE and BILLING_FROZEN. All other cases are listed in the error table below.

Errors

Errors that occur 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
HTTP400 CodeINVALID_REQUEST DescriptionEmpty query, query longer than 2000 characters, maxSteps outside the 1..10 range, provider not in the list of supported providers, includeDomains or excludeDomains with more than 50 elements
HTTP402 CodeINSUFFICIENT_BALANCE DescriptionThe portal balance has insufficient Ꝟ for a paid provider. Switch to BYOK or top up the balance
HTTP402 CodeBILLING_FROZEN DescriptionThe billing account is frozen
HTTP402 Codecompany_budget_exhausted DescriptionThe monthly company spend budget set by the portal administrator is exhausted. The scope field names the budget that was hit: USER — the caller's own budget, PORTAL — the budget of the whole portal. The canRequest field tells whether an increase can be requested: true for a personal budget, false for the portal one, which only an administrator raises. The rejection arrives only on a paid call. Research on your own BYOK key is free and keeps working
HTTP403 CodeSCOPE_DENIED DescriptionThe key lacks the vibe:search scope
HTTP404 CodePROVIDER_NOT_FOUND DescriptionA provider with this identifier does not exist or is unavailable
HTTP404 CodeCREDENTIAL_NOT_FOUND DescriptionThere is no USER, PORTAL, or PLATFORM key for the provider
HTTP404 CodePROVIDER_DOES_NOT_SUPPORT_RESEARCH DescriptionA 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
HTTP429 CodeRATE_LIMITED DescriptionThe limit of 20 requests per minute per portal — shared by all of its API keys — was exceeded. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas)
HTTP429 CodeSEARCH_DAILY_CAP_REACHED DescriptionWith this request, the Bitrix24 account's paid search spend for the current day in UTC+3 would exceed the cap set by the Bitrix24 account admin. The rejection arrives as a regular JSON response before the stream opens, the provider is not called and nothing is charged. The cap is shared by /v1/research and POST /v1/search. The response carries a Retry-After header, and the error body carries resetAt — the start of the next day in UTC+3 — and hint. Research on your own BYOK key is not limited by the cap
HTTP503 CodeFEATURE_NOT_ENABLED DescriptionDeep research is unavailable on this platform

Errors inside the SSE stream:

Code Description
CodeUPSTREAM_ERROR DescriptionThe provider returned an error during processing. No charge
CodePROVIDER_DOES_NOT_SUPPORT_RESEARCH DescriptionA server-side adapter guard — effectively a duplicate of the HTTP 404 code, surfacing when the catalog is out of sync
CodeINTERNAL_ERROR DescriptionInternal 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. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas). 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 use 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