For AI agents: markdown of this page — /docs-content-en/search/run.md documentation index — /llms.txt
Search
POST /v1/search
Performs a web search and returns a synthesized answer with links to sources. Supports synchronous mode and streaming via Server-Sent Events (stream: true). Deep research with a multi-step agentic loop is a separate endpoint POST /v1/research.
Request fields (body)
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
Fieldquery |
Typestring | Requiredyes | Default— | DescriptionQuery text. From 1 to 400 characters |
Fieldsearch_depth |
Typestring | Requiredno | Defaultbasic |
DescriptionSearch depth: basic or advanced. advanced costs more — switch to it only when the basic response lacks the data you need, see How to choose the depth. The price of each mode is listed in GET /v1/search/providers |
Fieldprovider |
Typestring | Requiredno | Defaultcascade USER → PORTAL → instance default engine | DescriptionForced engine choice: one of bitrix-search, tavily, brave, exa, you-com, linkup, perplexity, z-ai. jina is available only in POST /v1/research |
Fieldtopic |
Typestring | Requiredno | Defaultgeneral |
DescriptionSearch topic: general or news. Not all engines support the news mode — those that do populate publishedDate for news results, while the rest ignore it |
Fieldmax_results |
Typenumber | Requiredno | Default5 | DescriptionNumber of results in the response. From 1 to 20 |
Fieldmax_steps |
Typenumber | Requiredno | Default3 | DescriptionMaximum number of steps in agentic mode. From 1 to 5. Honored only by engines that support it — for bitrix-search this depends on the instance engine |
Fieldlang |
Typestring | Requiredno | Defaultru |
DescriptionSearch language: ru, en or auto. The default engine ignores this field — see "Known specifics" |
Fieldinclude_answer |
Typeboolean | Requiredno | Defaulttrue |
DescriptionInclude the synthesized answer in the answer field |
Fieldinclude_raw_content |
Typeboolean | Requiredno | Defaultfalse |
DescriptionRequest the full text of the pages found. For engines that support it, the text arrives in results[].rawContent |
Fieldinclude_images |
Typeboolean | Requiredno | Defaultfalse |
DescriptionRequest images for the query. For engines that support it, the links arrive in the top-level images array |
Fieldinclude_domains |
Typestring[] | Requiredno | Default[] |
DescriptionRestrict the search to the domains in the list. Up to 10 hostnames |
Fieldexclude_domains |
Typestring[] | Requiredno | Default[] |
DescriptionExclude domains. Up to 10 hostnames |
Fieldtime_range |
Typestring | null | Requiredno | Defaultnull |
DescriptionPublication time window: day, week, month, year |
Fieldstream |
Typeboolean | Requiredno | Defaultfalse |
Descriptiontrue — streaming via SSE instead of a JSON response |
Not all providers support every filter — see the table in Web Search for AI. An unsupported filter is ignored, and the response carries an X-Search-Filters-Ignored header with the list of skipped fields.
How to choose the depth
A request without search_depth runs in basic mode. advanced costs more. The prices of both modes on your instance are listed in GET /v1/search/providers.
| Task | Depth |
|---|---|
| TaskFind a fact, price, date, link or recent news item | Depthbasic |
| TaskGive a language model context for answering the user | Depthbasic |
| TaskRequests in a loop, on a schedule, or on every dashboard or widget refresh | Depthbasic |
TaskA one-off request when the basic response lacks the data you need |
Depthadvanced |
| TaskA summary of one topic across many sources | Depthadvanced |
| TaskMulti-step research with a report | DepthPOST /v1/research, not advanced in a loop |
Rule for agents and apps: send the request with basic first. Switch to advanced only if the basic response lacks the data you need. Do not make advanced your default.
A request with advanced differs from the examples below only in the search_depth value:
{
"query": "what is new in Cursor IDE",
"search_depth": "advanced",
"max_results": 5,
"lang": "auto"
}
Apps that refresh on their own — dashboards, widgets, scheduled runs — pay for every request. Spend per period is refresh rate × number of widgets × mode price. Cache the result on your side and do not repeat an identical request more often than the data changes.
Examples
curl — personal key
curl -X POST https://vibecode.bitrix24.com/v1/search \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "what is new in Cursor IDE",
"search_depth": "basic",
"max_results": 5,
"lang": "auto"
}'
curl — OAuth application
curl -X POST https://vibecode.bitrix24.com/v1/search \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "what is new in Cursor IDE",
"search_depth": "basic",
"max_results": 5,
"lang": "auto"
}'
JavaScript — personal key
const res = await fetch('https://vibecode.bitrix24.com/v1/search', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'what is new in Cursor IDE',
search_depth: 'basic',
max_results: 5,
lang: 'auto',
}),
})
const data = await res.json()
console.log(data.answer)
data.results.forEach((r) => console.log(`[${r.id}] ${r.title} — ${r.url}`))
JavaScript — OAuth application
const res = await fetch('https://vibecode.bitrix24.com/v1/search', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'what is new in Cursor IDE',
search_depth: 'basic',
max_results: 5,
lang: 'auto',
}),
})
const data = await res.json()
Response fields
| Field | Type | Description |
|---|---|---|
Fieldquery |
Typestring | DescriptionThe original query text |
Fieldprovider |
Typestring | DescriptionIdentifier of the provider that processed the request (e.g. bitrix-search, tavily) |
Fieldsearch_depth |
Typestring | DescriptionThe applied search depth: basic or advanced |
Fieldanswer |
Typestring | null | DescriptionSynthesized answer. Engines with citation support mark the sources with [N]. Comes back null when include_answer: false, and for engines that do not support synthesis: the capabilities.output.synthesizedAnswer flag in GET /v1/search/providers shows which engines do |
Fieldresults |
Typearray | DescriptionArray of found sources |
Fieldresults[].id |
Typenumber | DescriptionSequential number. Matches the [N] citation marker in answer for engines that emit citations |
Fieldresults[].url |
Typestring | DescriptionPage address |
Fieldresults[].title |
Typestring | DescriptionPage title |
Fieldresults[].content |
Typestring | DescriptionAlways a short description or snippet of the page. The full text arrives in a separate rawContent field, not here |
Fieldresults[].rawContent |
Typestring | null | DescriptionThe full page text when include_raw_content: true for engines that support it. Size capped at roughly 40 KB per result. null when the full text was not requested or the engine does not return it |
Fieldresults[].score |
Typenumber | null | DescriptionRelevance score from the provider. For tavily, a number from 0 to 1: the higher the value, the more relevant the result. Comes back null when the engine does not return a score: the capabilities.output.relevanceScore flag in GET /v1/search/providers shows which engines do |
Fieldresults[].publishedDate |
Typestring | null | DescriptionPublication date in ISO 8601 format. Comes back null when the engine does not return a date: the capabilities.output.publishedDate flag in GET /v1/search/providers shows which engines do. Engines that support topic: "news" populate it for news results |
Fieldimages |
Typearray | DescriptionTop-level array of images when include_images: true for engines that support it. Each element is an object with a url field. Empty array when images were not requested or the engine does not return them |
Fieldignored_filters |
Typestring[] | DescriptionThe passed filters and parameters the engine could not apply. The same list as the X-Search-Filters-Ignored header, delivered in the response body. May contain topic, include_images, include_domains, time_range, and others — the set depends on the engine |
Fieldsearch_id |
Typestring | DescriptionRequest identifier in the format ws_<14 digits>_<8 hex> |
Fieldupstream_search_id |
Typestring | null | DescriptionIdentifier at the external provider. Useful for incident investigation |
Fieldbilling_source |
Typestring | DescriptionOptional. Payment source: wallet — wallet, including usage beyond the AI quota, quota — Bitrix24 account AI quota, cowork — Cowork seat quota, byok — your own provider key. Omitted for a free request using a platform key |
Fieldcost_vibes |
Typenumber | DescriptionHow many Ꝟ were actually charged to the wallet. When billing_source is quota, cowork or byok, the value is 0 |
Fieldduration_ms |
Typenumber | DescriptionRequest processing duration in milliseconds |
Fieldpartial_charge |
Typeboolean | DescriptionPresent when parallel requests drained the remaining balance before the current charge completed. The request still succeeded, and the amount actually charged is less than the nominal cost — the total is in cost_vibes |
Fieldcharge_log_failed |
Typeboolean | DescriptionOptional. true if charging or writing the usage log failed. The successful search result is returned to the client. If the charge completed before the log failed, cost_vibes retains the amount actually charged to the wallet |
Response example
{
"query": "what is new in Cursor IDE",
"provider": "bitrix-search",
"search_depth": "basic",
"answer": "Cursor 3 — a redesign of the IDE interface. An agents window was added.",
"results": [
{
"id": 1,
"url": "https://cursor.com/blog/cursor-3",
"title": "Meet the new Cursor",
"content": "Cursor 3 brings a new agent-first interface...",
"rawContent": null,
"score": null,
"publishedDate": null
},
{
"id": 2,
"url": "https://cursor.com/changelog",
"title": "Changelog",
"content": "Latest features in Cursor IDE...",
"rawContent": null,
"score": null,
"publishedDate": null
}
],
"images": [],
"ignored_filters": [],
"search_id": "ws_20260430113025_a1b2c3d4",
"upstream_search_id": "AG_xyz",
"billing_source": "wallet",
"cost_vibes": 5,
"duration_ms": 8523
}
The provider value in the example (bitrix-search) depends on the instance: without an explicit provider, the request is handled by the default engine configured on the instance. The defaultProvider field in GET /v1/me shows the engine, and GET /v1/search/providers shows its pricing. The example shows a wallet charge. When using AI quota or Cowork seat quota, cost_vibes is 0. The payment source selection order is described under Pricing.
With include_raw_content: true on a supporting engine the rawContent field is populated with the full page text, and with include_images: true the top-level images array is populated with image links. If the engine does not support topic or include_images, those parameters land in ignored_filters.
Output safety. rawContent is unsanitized text from the open web, and images[] are third-party URLs. Before rendering in a browser, sanitize the text and proxy or validate the image links — do not insert them into the DOM as-is.
Streaming (SSE)
With stream: true the response arrives as an event stream with the text/event-stream content type. Each event is an event: <type> + data: <JSON> pair, separated by a blank line.
Possible events:
| Event | When it occurs | data fields |
|---|---|---|
Eventstart |
When it occursAt the start of the request | data fieldssearch_id, provider, search_depth |
Eventthinking |
When it occursIntermediate agent reasoning | data fieldscontent — a text fragment |
Eventtool_call |
When it occursThe agent called an internal tool | data fieldstool (web_search / content_extraction / external_tool), description |
Eventtool_result |
When it occursThe tool returned a result | data fieldsdescription, items_count |
Eventanswer_delta |
When it occursThe next fragment of the final answer | data fieldscontent — a chunk of text |
Eventdone |
When it occursThe final block | data fieldsThe same fields as in the synchronous response, including billing_source and cost_vibes |
Eventerror |
When it occursA failure during processing | data fieldserror.code, error.message |
Example stream:
event: start
data: {"search_id":"ws_20260430113025_a1b2c3d4","provider":"bitrix-search","search_depth":"basic"}
event: thinking
data: {"content":"Let's look into the sources now"}
event: tool_call
data: {"tool":"web_search","description":"web search query"}
event: tool_result
data: {"description":"got 5 sources","items_count":5}
event: answer_delta
data: {"content":"Cursor 3 — "}
event: answer_delta
data: {"content":"a redesign of the IDE interface."}
event: done
data: {"query":"what is new in Cursor IDE","provider":"bitrix-search","search_depth":"basic","answer":"Cursor 3 — a redesign of the IDE interface.","results":[{"id":1,"url":"https://cursor.com/blog/cursor-3","title":"Meet the new Cursor","content":"...","rawContent":null,"score":null,"publishedDate":null}],"images":[],"ignored_filters":[],"search_id":"ws_20260430113025_a1b2c3d4","upstream_search_id":"AG_xyz","billing_source":"wallet","cost_vibes":5,"duration_ms":8523}
Whether a real event stream with intermediate thinking / tool_call / answer_delta is emitted depends on the engine: for bitrix-search this varies by instance, while the other providers stream in buffered mode — only start and done arrive. A progressive stream is indicated by the capabilities.modes.streaming field in GET /v1/search/providers.
Error response example
402 — insufficient balance:
{
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient vibes for this search.",
"userMessage": "Insufficient vibes — 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": 5
}
}
The required field is the amount in Ꝟ needed for the request. The other situations are listed in the error table below.
Errors
Quota refusals arrive as regular HTTP responses with an error object before the SSE stream opens. Check the HTTP status and error.code before reading stream events.
| HTTP | Code | Description |
|---|---|---|
| HTTP400 | CodeINVALID_REQUEST |
DescriptionEmpty query, query longer than 400 characters, max_results outside the 1..20 range, max_steps outside the 1..5 range, provider outside the list of supported providers, an unknown value for search_depth, topic, lang or time_range, include_domains / exclude_domains longer than 10 elements |
| HTTP402 | CodeINSUFFICIENT_BALANCE |
DescriptionThe portal balance does not have enough Ꝟ for the selected mode |
| HTTP402 | CodeBILLING_FROZEN |
DescriptionThe billing account is frozen |
| HTTP402 | CodeAI_QUOTA_EXHAUSTED |
DescriptionAI quota is exhausted and wallet overage is unavailable. The reason is in error.reason, and the reset time is in error.resetAt. Wait for the reset, enable overage in your Vibecode account and top up the wallet, or use your own provider key. The optional error.learnMore field contains additional information |
| HTTP429 | CodeAI_PACING_LIMITED |
DescriptionThe AI quota pacing limit is reached. The response includes error.reason, error.overageDenied, error.resetAt and the Retry-After header in seconds. Wait for the specified time or use your own provider key |
| HTTP402 | CodeCOWORK_QUOTA_EXHAUSTED |
DescriptionA Cowork seat quota window is exhausted. The error.window field is fiveHour, week or month. Wait until error.resetAt or for the delay in the Retry-After header in seconds, or use your own provider key |
| HTTP402 | CodeCOWORK_SUBSCRIPTION_INACTIVE |
DescriptionThere is no active Cowork seat. Activate or resume the seat, or use your own provider key |
| 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 when paying from the wallet, including usage beyond the AI quota. A search made with your own BYOK key is free and keeps working |
| HTTP403 | CodeSCOPE_DENIED |
DescriptionThe key lacks the vibe:search scope |
| HTTP404 | CodePROVIDER_NOT_FOUND |
DescriptionThe provider passed does not exist in the system or is disabled |
| HTTP404 | CodeCREDENTIAL_NOT_FOUND |
DescriptionThe provider has no USER, PORTAL, or PLATFORM key |
| HTTP404 | CodePROVIDER_DOES_NOT_SUPPORT_SEARCH |
Descriptionprovider: "jina" was requested — Jina works only in POST /v1/research. The search_id field is present in the response |
| HTTP429 | CodeRATE_LIMITED |
DescriptionExceeded the limit of 60 requests per minute per portal, shared across all of the portal's API keys. The current limit is returned in the x-ratelimit-limit header |
| HTTP429 | CodeSEARCH_DAILY_CAP_REACHED |
DescriptionWith this request, total search usage from the wallet, AI quota and Cowork seat quota for the current day in UTC+3 would exceed the cap set by the Bitrix24 account admin. The provider is not called and nothing is charged. The cap is shared by /v1/search and POST /v1/research. The response carries a Retry-After header, and the error body carries resetAt — the start of the next day in UTC+3 — and hint. Searches on your own BYOK key are not limited by the cap |
| HTTP500 | CodeINTERNAL_ERROR |
DescriptionInternal server error. The response contains search_id |
| HTTP401/403/429/502 | CodeUPSTREAM_ERROR |
DescriptionThe provider returned an error; no charge. The upstream_status field in the response carries the provider's original status. For a BYOK key, provider 401 and 403 responses keep their status — the provider rejected your key, so retrying without replacing it will not help. A provider 429 keeps its status for any key and carries the Retry-After header — retry later. Every other provider error, including a rejected platform-engine key, arrives as 502 — retry the request |
| HTTP503 | CodeFEATURE_NOT_ENABLED |
DescriptionWeb Search is unavailable on this platform |
| HTTP503 | CodeUPSTREAM_TIMEOUT |
DescriptionThe provider exceeded the request timeout. No charge. The response carries the Retry-After: 30 header — retry the request after the time it specifies |
For the full list of common API errors, see Errors.
Known specifics
Key selection cascade. When the provider field is omitted, the platform picks the source in the order: user key (USER BYOK) → Bitrix24 account key (PORTAL BYOK) → the default engine configured on the instance (shown by the defaultProvider field in GET /v1/me). Your own BYOK key with isDefault: true is used automatically — you do not need to pass provider.
The charge happens only after a successful response. Before calling the provider, the platform checks whether payment can proceed from the selected source. Wallet payments, including AI quota overage, require balance and company budget checks. Provider errors consume neither quota nor wallet funds.
The default engine ignores lang in /v1/search. It does not forward lang to the upstream provider — the field always appears in X-Search-Filters-Ignored.
An empty result set is charged. When the provider found no sources and returned results: [], the successful request consumes quota or wallet funds according to the selected payment source. The charge covers the provider's processing of the request, not the number of results.
An accounting failure does not invalidate the search result. When charge_log_failed: true, do not resend the request just to write the usage log: another search is a separate request with a separate charge.
provider: "jina" is available only in /v1/research. A request with provider: "jina" on the current endpoint returns 404 PROVIDER_DOES_NOT_SUPPORT_SEARCH. The list of providers supporting /v1/search is checked via the capabilities.modes.search.basic or capabilities.modes.search.advanced field in GET /v1/search/providers.
A Bitrix24 account admin can turn off the advanced mode. Paid search through the platform engine with search_depth: "advanced" then runs as basic, and the request is not rejected. The response and the SSE start and done events carry search_depth: "basic", and the response headers, synchronous and streaming alike, include X-Search-Depth-Downgraded: advanced. The charge follows the basic price, and the request counts toward the daily spend cap at that price. Search on your own BYOK key is not affected.