
# Web Search for AI

REST endpoints for web search on behalf of AI agents and applications. A single request returns a synthesized answer with links to sources; streaming mode delivers results as they become ready. For deep research with a multi-step agentic loop, use the separate [`POST /v1/research`](/docs/search/research) endpoint.

**Scope:** `vibe:search` | **Base URL:** `https://vibecode.bitrix24.com/v1` | **Authorization:** `X-Api-Key`

[Quickstart](#quickstart) | [Endpoint reference](#endpoint-reference) | [Pricing](#pricing) | [Error codes](#error-codes) | [RAG recipe with LLM](/docs/recipes/web-search-with-llm)

## Documentation sections

- [Search](/docs/search/run) — synchronous request and streaming (SSE) via `POST /v1/search`
- [Deep research](/docs/search/research) — multi-step agentic research `POST /v1/research`
- [Providers](/docs/search/providers) — list of search engines and their capabilities
- [Your own keys (BYOK)](/docs/search/credentials) — adding personal keys for free search

## Which key to choose

Web Search works with two types of keys. The choice depends on whether you send the request on your own behalf or on behalf of an end user.

| Scenario | Key | Request headers |
|---------|------|-------------------|
| Your own Bitrix24 account, personal script or server | Personal API key `vibe_api_…` | `X-Api-Key: vibe_api_…` |
| Request on behalf of an end user in an OAuth application | Authorization key `vibe_app_…` | `X-Api-Key: vibe_app_…` + `Authorization: Bearer <session_token>` |

More on key formats and obtaining a `session_token` — [Keys and authorization](/docs/keys-auth).

## Providers

The platform offers platform-managed engines billed in Vibe credits, plus BYOK providers billed directly by the vendor. Which engines are available on a given instance, which one is platform-managed, and what it costs depend on the instance configuration. The current list is returned by [`GET /v1/search/providers`](/docs/search/providers).

| Provider | Purpose | Pricing |
|-----------|-----------|-----------|
| `bitrix-search` | Platform web search. The specific engine and its capabilities depend on the instance — see [`GET /v1/search/providers`](/docs/search/providers) | platform-managed, price in Vibe credits — see [`GET /v1/search/providers`](/docs/search/providers) |
| `tavily` | Tavily — web search with domain and time filters, relevance scoring | 0 Ꝟ (BYOK) |
| `brave` | Brave Search — private web search, optional summarizer in `advanced` | 0 Ꝟ (BYOK) |
| `exa` | Exa — neural/semantic search with full content extraction | 0 Ꝟ (BYOK) |
| `you-com` | You.com — search with citations and follow-up questions | 0 Ꝟ (BYOK) |
| `linkup` | Linkup — real-time web crawl with deep research mode | 0 Ꝟ (BYOK) |
| `perplexity` | Perplexity Sonar — Sonar models for search and deep research | 0 Ꝟ (BYOK) |
| `jina` | Jina DeepSearch — iterative "search → read → think" loop, research only | 0 Ꝟ (BYOK) |
| `z-ai` | Z.AI Web Search — search without a synthesized answer, with publication dates | 0 Ꝟ (BYOK) |

The current list with the capability matrix and prices — [`GET /v1/search/providers`](/docs/search/providers). Which provider is platform-managed, its price, and the default engine vary from instance to instance. All BYOK providers require your own key, which you add via [Your own keys (BYOK)](/docs/search/credentials). BYOK providers always cost 0 Ꝟ on the platform side.

## Supported provider capabilities

The full capability matrix is available in [`GET /v1/search/providers`](/docs/search/providers) — `capabilities` for each provider. The key subsets are shown below in three tables, one per group.

### Modes and streaming

| Provider | `basic` | `advanced` | `research` | Stream |
|-----------|:-------:|:----------:|:----------:|:------|
| `bitrix-search` | ✓ | ✓ | instance-dependent | instance-dependent |
| `tavily` | ✓ | ✓ | ✓ | buffered |
| `brave` | ✓ | ✓ | ✗ | buffered |
| `exa` | ✓ | ✓ | ✓ | buffered |
| `you-com` | ✓ | ✓ | ✓ | buffered |
| `linkup` | ✓ | ✓ | ✓ | buffered |
| `perplexity` | ✓ | ✓ | ✓ | buffered |
| `jina` | ✗ | ✗ | ✓ | buffered |
| `z-ai` | ✓ | ✓ | ✗ | buffered |

Progressive streaming emits intermediate `thinking` / `tool_call` / `answer_delta` events. Buffered streaming sends `start` and `done`; in `research` mode, provider events arrive between them: `tool_call` and `tool_result` from `tavily`, `section_complete` from `linkup`.

### Synthesized answer and formatting

| Provider | `answer` | Citations `[N]` | `score` | `publishedDate` | Follow-up questions |
|-----------|:--------:|:------------:|:-------:|:----------------:|:-------------------:|
| `bitrix-search` | ✓ | instance-dependent | instance-dependent | instance-dependent | ✗ |
| `tavily` | ✓ | ✗ | ✓ | ✓ | ✗ |
| `brave` | in `advanced` | ✗ | ✗ | ✓ | ✗ |
| `exa` | ✓ | ✓ | ✓ | ✓ | ✗ |
| `you-com` | ✓ | ✓ | ✗ | ✓ | ✓ |
| `linkup` | ✓ | ✓ | ✗ | ✓ | ✗ |
| `perplexity` | ✓ | ✓ | ✗ | ✗ | ✗ |
| `jina` | ✓ | ✓ | ✗ | ✓ | ✗ |
| `z-ai` | ✗ | ✗ | ✗ | ✓ | ✗ |

For `brave`, the `answer` field is returned only with `search_depth: "advanced"`, via the optional summarizer. In `basic` it is `null`.

### Filters

| Provider | `include_domains` | `exclude_domains` | `time_range` |
|-----------|:-----------------:|:-----------------:|:------------:|
| `bitrix-search` | instance-dependent | instance-dependent | instance-dependent |
| `tavily` | ✓ | ✓ | ✓ |
| `brave` | ✗ | ✗ | ✓ |
| `exa` | ✓ | ✓ | ✗ |
| `you-com` | ✗ | ✗ | ✗ |
| `linkup` | ✗ | ✗ | ✗ |
| `perplexity` | ✗ | ✗ | ✗ |
| `jina` | ✓ | ✓ | ✗ |
| `z-ai` | ✓ | ✗ | ✓ |

When the selected provider does not support a passed filter, the request completes without error — the filter is ignored. The response carries an `X-Search-Filters-Ignored` header with the list of skipped fields:

```
X-Search-Filters-Ignored: include_domains,time_range
```

For `brave` with `include_answer: true` and `search_depth: "basic"`, an additional `X-Answer-Not-Supported: brave-search-does-not-synthesize-answers` header is returned, and the `answer` field in JSON is `null`. In `advanced` mode the same provider engages the summarizer and returns a populated `answer`.

## When to choose which provider

Quick guidelines:

- **`bitrix-search`** — platform web search, requires no key of your own (billed in Vibe credits). Returns a synthesized `answer` with sources — ready-made RAG output for AI agents, with no separate assembly step. Its specific capabilities (the `research` mode, progressive SSE streaming, filters) depend on the instance — the exact matrix is in [`GET /v1/search/providers`](/docs/search/providers).
- **`tavily`** — domain filters (`include_domains` / `exclude_domains`), publication time window (`time_range`), numeric relevance score (`score`). A universal choice for general-purpose queries.
- **`brave`** — web search without `answer` synthesis by default; the summarizer is enabled in `advanced` mode. Suitable for privacy-focused queries.
- **`exa`** — neural/semantic search. Suitable for research tasks that need semantically related pages rather than an exact keyword match.
- **`you-com`** — the only provider with follow-up questions in research mode.
- **`linkup`** — real-time web crawl (`liveData: true`), suitable for queries about events from the last few hours.
- **`perplexity`** — Sonar models, OpenAI-compatible format, deep research with citations.
- **`jina`** — research mode only, an iterative "search → read → think" loop. Not available via [`POST /v1/search`](/docs/search/run).
- **`z-ai`** — search without a synthesized answer, returns results with publication dates.

## Quickstart

A minimal call to [`POST /v1/search`](/docs/search/run): the question text in the `query` field and `basic` mode. The response contains a synthesized `answer` and a `results` array with the pages found; whether citations are included depends on the instance engine — see the capability matrix. When to switch to `advanced` — see [How to choose the depth](/docs/search/run#how-to-choose-the-depth).

```bash
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
  }'
```

Response:

```json
{
  "query": "what is new in Cursor IDE",
  "provider": "bitrix-search",
  "search_depth": "basic",
  "answer": "The latest update in Cursor IDE, specifically Cursor 3, introduces a unified workspace for building software with agents, featuring a faster, cleaner, and more powerful interface with a multi-repo layout, seamless handoff between local and cloud agents, and the option to switch back to the Cursor IDE at any time. …",
  "results": [
    {
      "id": 1,
      "url": "https://cursor.com/blog/cursor-3",
      "title": "Meet the new Cursor · Cursor",
      "content": "...",
      "score": 0.8565368,
      "publishedDate": null
    },
    {
      "id": 2,
      "url": "https://thenewstack.io/cursor-3-demotes-ide",
      "title": "Cursor's $2 billion bet: The IDE is now a fallback, not the default - The New Stack",
      "content": "...",
      "score": 0.8259413,
      "publishedDate": null
    }
  ],
  "images": [],
  "ignored_filters": ["lang"],
  "search_id": "ws_20261021221143_d248ca4d",
  "upstream_search_id": null,
  "billing_source": "wallet",
  "cost_vibes": 0.02,
  "duration_ms": 2670
}
```

Citation markers such as `[1]`, `[2]` in the `answer` field, when the engine adds them, correspond to `results[].id` values. The `provider` value in the response (here `bitrix-search`) depends on the instance — without an explicit `provider`, the request goes through the default engine configured on the instance. The current default is shown by the `defaultProvider` field in [`GET /v1/me`](/docs/keys-auth).

## Full example

An end-to-end scenario: find the instance's platform provider and the price of `basic` mode, run a search, and print the answer with the list of sources.

```javascript
const VIBE_KEY = process.env.VIBE_KEY
const BASE = 'https://vibecode.bitrix24.com/v1'

async function api(method, path, body = null) {
  const opts = { method, headers: { 'X-Api-Key': VIBE_KEY } }
  if (body) {
    opts.headers['Content-Type'] = 'application/json'
    opts.body = JSON.stringify(body)
  }
  const res = await fetch(`${BASE}${path}`, opts)
  const data = await res.json()
  if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`)
  return data
}

// 1. Find out which provider is platform-managed on this instance and what basic mode costs
const { providers } = await api('GET', '/search/providers')
const platform = providers.find(p => p.platformManaged)
console.log('Platform provider:', platform.slug, '— basic:', platform.pricing.basic, 'Ꝟ')

// 2. Search in basic mode. Without the provider field the request follows the key selection cascade
const result = await api('POST', '/search', {
  query: 'what is new in Cursor IDE',
  search_depth: 'basic',
  max_results: 5,
})
console.log('Answered by:', result.provider, '— payment source:', result.billing_source)
console.log('Charged to wallet:', result.cost_vibes, 'Ꝟ')

// 3. Print the answer and the sources: an [N] marker in answer points to the results item whose id equals N
console.log(result.answer)
for (const source of result.results) console.log(`[${source.id}]`, source.title, source.url)
```

Pairing `/v1/search` and `/v1/ai/chat/completions`, where the LLM answers from fresh sources with `[N]` citation markers, is covered in the recipe [Web search + LLM (RAG)](/docs/recipes/web-search-with-llm).

## Pricing

Cost is expressed in the platform's virtual currency — Vibe credits (Ꝟ). The charge occurs after a successful provider response. On a provider error the balance is unchanged.

The platform provider's pricing varies from instance to instance — which engine is platform-managed and how many Ꝟ its `basic` / `advanced` / `research` modes cost are returned by [`GET /v1/search/providers`](/docs/search/providers) in the `pricing` field. BYOK providers (`tavily`, `brave`, `exa`, `you-com`, `linkup`, `perplexity`, `jina`, `z-ai`) always cost 0 Ꝟ on the platform side — you pay the vendor directly for those requests, at the vendor's own rates.

Paid search first uses the available Bitrix24 account AI quota, while a request with a Cowork key uses the seat quota. Usage beyond the AI quota uses the wallet when overage is enabled and available. When AI quota does not apply to the request, payment comes from the wallet. Quota or wallet charges occur after a successful provider response. A positive wallet balance does not override AI quota limits or Cowork seat quota windows.

In the synchronous search response and the `done` event of search and research streams, the `billing_source` field shows the payment source:

| Value | Payment source |
|----------|-----------------|
| `wallet` | Wallet, including usage beyond the AI quota |
| `quota` | Bitrix24 account AI quota |
| `cowork` | Cowork seat quota |
| `byok` | Your own provider key — BYOK, Bring Your Own Key. The platform AI quota and wallet are not used |

The `billing_source` field is omitted for a free request using a platform key. The `cost_vibes` field shows only the amount actually charged to the wallet. It is `0` when using AI quota, Cowork seat quota or your own key.

Search usage counts toward the [company AI quota](/docs/ai/consumption/quota). The `byModel` array in the `GET /v1/ai/quota` response may include a row with the reserved `modelId: "web-search"`. This is the quota share used by web search, rather than a model in the catalogue. The `calls` and `pctOfLimit` fields show the request count and share of the monthly limit.

Request history broken down by provider, Vibe credits spent by period, and average duration are available on the `/search` page in your Vibecode account. There you can also add, test, and delete BYOK keys through a visual interface. The "Use" button next to each provider opens a dialog with ready-made snippets in cURL, JavaScript, Python, the TypeScript SDK, and a text prompt for AI assistants.

## Rate limit

- 60 requests per minute per Bitrix24 account for [`POST /v1/search`](/docs/search/run).
- 20 requests per minute per Bitrix24 account for [`POST /v1/research`](/docs/search/research) — deep research takes tens of times longer than a regular search.

The limit is shared by all API keys of the Bitrix24 account — they draw from the same pool. Spreading the load across several keys does not raise the ceiling. The effective value of the endpoint's own rate limit is returned in the `x-ratelimit-limit` header.

When exceeded, `429 RATE_LIMITED` is returned.

## Endpoint reference

| Method | Path | Description |
|-------|------|----------|
| POST | [`/v1/search`](/docs/search/run) | Synchronous request or streaming |
| POST | [`/v1/research`](/docs/search/research) | Deep research with a multi-step agentic loop, SSE only |
| GET | [`/v1/search/providers`](/docs/search/providers) | List of providers with the capability matrix and pricing |
| GET | [`/v1/search/credentials`](/docs/search/credentials/list) | List of your BYOK keys |
| POST | [`/v1/search/credentials`](/docs/search/credentials/create) | Add a BYOK key for the selected provider |
| DELETE | [`/v1/search/credentials/:id`](/docs/search/credentials/delete) | Delete a BYOK key |
| POST | [`/v1/search/credentials/:id/test`](/docs/search/credentials/test) | Test a BYOK key |

## Key selection cascade

When `provider` is not passed in the [`POST /v1/search`](/docs/search/run) request, the platform selects it in the following 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`](/docs/keys-auth)).

For [`POST /v1/research`](/docs/search/research) the cascade works differently. If the `provider` field is omitted, the server immediately substitutes the default research engine configured on the instance — a USER/PORTAL default for a different provider (e.g. `exa`) is not considered here. If that engine has no USER, PORTAL, or PLATFORM key, `404 CREDENTIAL_NOT_FOUND` is returned. The USER → PORTAL → PLATFORM cascade is applied **within** the selected provider. To go through a different research provider, pass `provider` explicitly.

Once you add your own BYOK key with `isDefault: true`, the platform uses it automatically — the `provider` parameter can be omitted.

## Migration from Tavily

The request schema mirrors the Tavily Search API. Differences:

- Endpoint: `https://api.tavily.com/search` → `https://vibecode.bitrix24.com/v1/search`.
- Authorization: the `api_key` field in the body → the `X-Api-Key: YOUR_API_KEY` header.
- Added an optional `provider` field — choice of engine from the available list.
- Added optional fields: `topic` (`general` or `news`) and `include_images` — request images in the response. Tavily has these capabilities, but other engines do not. Unsupported fields are ignored and listed in `ignored_filters`.

If you have your own Tavily key, add it via [`POST /v1/search/credentials`](/docs/search/credentials/create) and use `provider: "tavily"` or make the key the default. The other request fields work identically.

## Error codes

### Web search errors

| Code | HTTP | Description |
|-----|------|---------|
| `INVALID_REQUEST` | 400 | Validation failed — empty `query`, `query` longer than the limit, unknown value for `provider`, `search_depth`, `topic`, `lang` or `time_range`, `include_domains` / `exclude_domains` limits exceeded |
| `INSUFFICIENT_BALANCE` | 402 | The portal balance does not have enough Ꝟ for the selected mode. Switch to BYOK or top up the balance |
| `BILLING_FROZEN` | 402 | The billing account is frozen — top up the balance and remove the block in your Vibecode account |
| `AI_QUOTA_EXHAUSTED` | 402 | AI 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 |
| `AI_PACING_LIMITED` | 429 | The 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 |
| `COWORK_QUOTA_EXHAUSTED` | 402 | A 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 |
| `COWORK_SUBSCRIPTION_INACTIVE` | 402 | There is no active Cowork seat. Activate or resume the seat, or use your own provider key |
| `PROVIDER_NOT_FOUND` | 404 | The `provider` passed is not in the system or is unavailable |
| `CREDENTIAL_NOT_FOUND` | 404 | The requested provider has no BYOK key for the user or portal, and there is no platform key |
| `PROVIDER_DOES_NOT_SUPPORT_SEARCH` | 404 | `provider: "jina"` was requested in [`POST /v1/search`](/docs/search/run) — Jina works only in [`POST /v1/research`](/docs/search/research) |
| `PROVIDER_DOES_NOT_SUPPORT_RESEARCH` | 404 | `brave` or `z-ai` — or `bitrix-search` when the instance engine lacks research — was requested in [`POST /v1/research`](/docs/search/research). These providers work only in [`POST /v1/search`](/docs/search/run). On an instance where `bitrix-search` maps to a research-capable engine, `/v1/research` accepts it |
| `RATE_LIMITED` | 429 | The per-minute request limit per portal was exceeded |
| `UPSTREAM_ERROR` | 401/403/429/502 | 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 |
| `FEATURE_NOT_ENABLED` | 503 | Web Search or deep research is unavailable on this platform |
| `UPSTREAM_TIMEOUT` | 503 | The provider exceeded the request timeout. The response carries the `Retry-After: 30` header — retry the request after the time it specifies |

### BYOK key errors

| Code | HTTP | Description |
|-----|------|---------|
| `INVALID_CREDENTIAL` | 400 | When creating a BYOK key, the provider rejected it during the pre-flight check. The record was not saved |
| `INVALID_REQUEST` | 400 | `apiKey` not specified, invalid `provider`, name longer than 64 characters |

### System errors

| Code | HTTP | Description |
|-----|------|---------|
| `MISSING_API_KEY` | 401 | The `X-Api-Key` header is missing |
| `INVALID_API_KEY` | 401 | The key is not recognized — no such key exists on the platform |
| `KEY_INACTIVE` | 401 | The key is revoked or blocked by the platform. An expired key has its own code, `KEY_EXPIRED` |
| `SCOPE_DENIED` | 403 | The key lacks the `vibe:search` scope |
| `INTERNAL_ERROR` | 500 | Internal server error |

For the full list of common API errors, see [Errors](/docs/errors).

## Error response structure

```json
{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Insufficient vibes for this search. 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": 5,
    "topupUrl": "https://vibecode.bitrix24.com/r/upgrade?c=ai_quota_copay&sc=api&m=api&t=topup&p=…&e=…&s=…"
  }
}
```

The `userMessage`, `required`, and `topupUrl` fields are returned on the `INSUFFICIENT_BALANCE` and `BILLING_FROZEN` billing errors. The `required` field is the amount in Ꝟ needed for the request, and `topupUrl` is a link to top up the balance. The `hint` field is returned on those same billing errors and on `CREDENTIAL_NOT_FOUND`, where it explains how to add your own BYOK key for the selected provider.

The quota refusals `AI_QUOTA_EXHAUSTED`, `AI_PACING_LIMITED`, `COWORK_QUOTA_EXHAUSTED` and `COWORK_SUBSCRIPTION_INACTIVE` use the same `error` object. They arrive as regular HTTP responses before the SSE stream opens. Handle the status and `error.code` before reading stream events. The `error.learnMore` field may contain additional information for `AI_QUOTA_EXHAUSTED`.

## See also

- [Search (POST /v1/search)](/docs/search/run)
- [Deep research (POST /v1/research)](/docs/search/research)
- [Providers](/docs/search/providers)
- [Your own keys (BYOK)](/docs/search/credentials)
- [Web search + LLM (RAG)](/docs/recipes/web-search-with-llm)
- [Keys and authorization](/docs/keys-auth)
- [AI Router](/docs/ai)
- [Limits and optimization](/docs/optimization)
- [Errors](/docs/errors)
