Dành cho AI agent: markdown của trang này — /docs-content-en/search/run.md chỉ mục tài liệu — /llms.txt

Hiện tại, các bài viết trong tài liệu chỉ có bằng tiếng Anh.

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

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
Find a fact, price, date, link or recent news item basic
Give a language model context for answering the user basic
Requests in a loop, on a schedule, or on every dashboard or widget refresh basic
A one-off request when the basic response lacks the data you need advanced
A summary of one topic across many sources advanced
Multi-step research with a report POST /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:

JSON
{
  "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

Terminal
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

Terminal
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

javascript
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

javascript
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
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]. 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
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. 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
results[].publishedDate string | null Publication 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
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

JSON
{
  "query": "what is new in Cursor IDE",
  "provider": "bitrix-search",
  "search_depth": "basic",
  "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":"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 [1]."}

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 [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:

JSON
{
  "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 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
402 INSUFFICIENT_BALANCE The portal balance does not have enough Ꝟ for the selected mode
402 BILLING_FROZEN The billing account is frozen
402 company_budget_exhausted The 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. A search made with your own BYOK key is free and keeps working
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 no USER, PORTAL, or 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. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas)
429 SEARCH_DAILY_CAP_REACHED With this request, the Bitrix24 account's paid search spend for the current day in Moscow time 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 Moscow time — and hint. Searches on your own BYOK key are not limited by the cap
500 INTERNAL_ERROR Internal server error. The response contains search_id
401/403/429/502 UPSTREAM_ERROR The 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
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 are 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.

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.

See also