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 |
|---|---|---|---|---|
query |
string | yes | — | Query text. From 1 to 400 characters |
search_depth |
string | no | basic |
Search depth: basic or advanced. The price of each mode is listed in GET /v1/search/providers |
provider |
string | no | cascade USER → PORTAL → instance default engine | Forced engine choice: one of bitrix-search, tavily, brave, exa, you-com, linkup, perplexity, z-ai. jina is available only in POST /v1/research |
topic |
string | no | general |
Search topic: general or news. Not all engines support the news mode — those that do populate publishedDate for news results, while the rest ignore it |
max_results |
number | no | 5 | Number of results in the response. From 1 to 20 |
max_steps |
number | no | 3 | Maximum 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 |
lang |
string | no | ru |
Search language: ru, en or auto. The default engine ignores this field — see "Known specifics" |
include_answer |
boolean | no | true |
Include the synthesized answer in the answer field |
include_raw_content |
boolean | no | false |
Request the full text of the pages found. For engines that support it, the text arrives in results[].rawContent |
include_images |
boolean | no | false |
Request images for the query. For engines that support it, the links arrive in the top-level images array |
include_domains |
string[] | no | [] |
Restrict the search to the domains in the list. Up to 10 hostnames |
exclude_domains |
string[] | no | [] |
Exclude domains. Up to 10 hostnames |
time_range |
string | null | no | null |
Publication time window: day, week, month, year |
stream |
boolean | no | false |
true — 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.
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": "advanced",
"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": "advanced",
"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: 'advanced',
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: 'advanced',
max_results: 5,
lang: 'auto',
}),
})
const data = await res.json()
Response fields
| Field | Type | Description |
|---|---|---|
query |
string | The original query text |
provider |
string | Identifier of the provider that processed the request (e.g. bitrix-search, tavily) |
search_depth |
string | The applied search depth: basic or advanced |
answer |
string | null | Synthesized answer. Engines with citation support mark the sources with [N] — see the capability table in the section index. null for brave or when include_answer: false |
results |
array | Array of found sources |
results[].id |
number | Sequential number. Matches the [N] citation marker in answer for engines that emit citations |
results[].url |
string | Page address |
results[].title |
string | Page title |
results[].content |
string | Always a short description or snippet of the page. The full text arrives in a separate rawContent field, not here |
results[].rawContent |
string | null | The 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 |
results[].score |
number | null | Relevance score from the provider — a number from 0 to 1: the higher the value, the more relevant the result. null for brave. For bitrix-search it depends on the instance engine — see the capability table in the section index |
results[].publishedDate |
string | null | Publication date in ISO 8601 format. Populated mainly when topic: "news". For bitrix-search it depends on the instance engine — see the capability table in the section index |
images |
array | Top-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 |
ignored_filters |
string[] | The 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 |
search_id |
string | Request identifier in the format ws_<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 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 |
charge_log_failed |
boolean | Present when the usage log could not be written. The result was delivered, no funds were charged (cost_vibes: 0) |
Response example
{
"query": "what is new in Cursor IDE",
"provider": "bitrix-search",
"search_depth": "advanced",
"answer": "Cursor 3 — a redesign of the IDE interface [1]. An agents window was added [2].",
"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",
"cost_vibes": 5,
"duration_ms": 8523
}
The provider value in the example (bitrix-search) and cost_vibes depend on the instance: without an explicit provider, the request is handled by the default engine configured on the instance (shown by the defaultProvider field in GET /v1/me), and the charged amount is its price from GET /v1/search/providers.
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 |
|---|---|---|
start |
At the start of the request | search_id, provider, search_depth |
thinking |
Intermediate agent reasoning | content — a text fragment |
tool_call |
The agent called an internal tool | tool (web_search / content_extraction / external_tool), description |
tool_result |
The tool returned a result | description, items_count |
answer_delta |
The next fragment of the final answer | content — a chunk of text |
done |
The final block | the same fields as in the synchronous response |
error |
A failure during processing | error.code, error.message |
Example stream:
event: start
data: {"search_id":"ws_20260430113025_a1b2c3d4","provider":"bitrix-search","search_depth":"advanced"}
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 [1]."}
event: done
data: {"query":"what is new in Cursor IDE","provider":"bitrix-search","search_depth":"advanced","answer":"Cursor 3 — a redesign of the IDE interface [1].","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","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
| HTTP | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST |
Empty query, query longer than 400 characters, max_results outside the 1..20 range, max_steps outside the 1..5 range, provider not from the list of supported providers, unknown value for search_depth, topic, lang or time_range, include_domains / exclude_domains longer than 10 elements |
| 402 | INSUFFICIENT_BALANCE |
The portal balance does not have enough Ꝟ for the selected mode |
| 402 | BILLING_FROZEN |
The billing account is frozen |
| 403 | SCOPE_DENIED |
The key lacks the vibe:search scope |
| 404 | PROVIDER_NOT_FOUND |
The provider passed does not exist in the system or is disabled |
| 404 | CREDENTIAL_NOT_FOUND |
The provider has neither a USER, PORTAL, nor PLATFORM key |
| 404 | PROVIDER_DOES_NOT_SUPPORT_SEARCH |
provider: "jina" was requested — Jina works only in POST /v1/research. The search_id field is present in the response |
| 429 | RATE_LIMITED |
Exceeded the limit of 60 requests per minute per portal, shared across all of the portal's API keys |
| 500 | INTERNAL_ERROR |
Internal server error. The response contains search_id |
| 502 | UPSTREAM_ERROR |
The provider returned an error. No charge |
| 503 | FEATURE_NOT_ENABLED |
Web Search is unavailable on this platform |
| 503 | UPSTREAM_TIMEOUT |
The provider exceeded the request timeout. No charge. The response carries the Retry-After: 30 header — retry the request after the time it specifies |
The full list of common API errors — 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 the request the balance is checked: if insufficient, 402 INSUFFICIENT_BALANCE is returned without calling the provider. On UPSTREAM_ERROR or UPSTREAM_TIMEOUT the balance does not change — the provider returned no result, so there is nothing to charge for.
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 in full. When the provider found no sources and returned results: [], cost_vibes is still charged in full. This is by design: the charge covers the provider's processing of the request, not the number of results.
The charge_log_failed field signals a lost audit record. If the provider responds successfully but the usage log cannot be written, the response is still delivered to the client with cost_vibes: 0 and the charge_log_failed: true flag. No Vibe credits were charged — the audit log for this request is also absent. The scenario is rare and is visible in the AI Search section (/search).
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.
See also
- Web Search for AI — section overview, pricing, migration from Tavily
- Deep Research (POST /v1/research) — multi-step agentic research
- Providers — list of engines with prices and capabilities
- Your own keys (BYOK) — adding BYOK keys for eight providers
- Web search + LLM (RAG) — recipe pairing with
/v1/ai/chat/completionsfor answers with citations - Keys and authorization — personal key and authorization key
- Errors — API error code reference
- Limits and optimization — common request rate rules