> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.airweave.ai/search/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.airweave.ai/_mcp/server. # Search > Search API reference for Airweave ## Instant Search `POST /collections/{id}/search/instant` Direct vector search. Use when speed is critical (\~0.5sec). The only parameter unique to instant is `retrieval_strategy`, which controls how the vector database matches your query: * **`hybrid`** (default) — Combines semantic and keyword search via Reciprocal Rank Fusion. Best for most queries. * **`semantic`** — Dense vector cosine similarity. Finds conceptually similar content even when wording differs. * **`keyword`** — BM25 text matching. Only returns content with your exact terms. Use for error codes, identifiers, or known phrases. In classic and agentic search, the retrieval strategy is chosen automatically. ## Classic Search `POST /collections/{id}/search/classic` AI-optimized search strategy. Sensible default for most use cases (\~2sec). An LLM analyzes your query and generates an optimized search strategy. ## Agentic Search `POST /collections/{id}/search/agentic` Agent that navigates through your collection to find the best results. Use when recall matters more than latency (\<2min). An AI agent iteratively searches your data using tool calling. It searches with multiple strategies, reads full documents, navigates entity hierarchies (parent/child/sibling), and builds a comprehensive result set. Two parameters unique to agentic: * **`thinking`** — Enables extended chain-of-thought reasoning before tool calls. Better search strategies, but slower and uses more tokens. Useful for complex or ambiguous queries. * **`limit`** — Unlike instant/classic where the vector database always returns up to `limit` results, the agent collects results based on relevance. It may return fewer if it decides there aren't enough matches. Setting a limit caps the maximum — if the agent collects more, results are truncated. When `null` (default), there is no cap. ### Streaming `POST /collections/{id}/search/agentic/stream` Real-time SSE events as the agent works. Events are delivered as `data: {json}\n\n` messages. The stream terminates after a `done` or `error` event. #### started Emitted once when the search begins. ```json { "type": "started", "request_id": "req-abc123", "tier": "agentic", "collection_readable_id": "my-collection", "query": "What authentication methods do we support?", "thinking": true, "filter": null, "limit": null } ``` #### thinking Emitted once per iteration after the LLM responds. `thinking` contains extended reasoning (when enabled), `text` contains conversational output before tool calls. ```json { "type": "thinking", "thinking": "The user is asking about authentication methods. I should search for docs about auth, SSO, API keys...", "text": "Searching for authentication documentation...", "duration_ms": 2340, "diagnostics": { "iteration": 0, "prompt_tokens": 4521, "completion_tokens": 892 } } ``` #### tool\_call Emitted after each tool the agent calls. `diagnostics.arguments` has the full tool input, `diagnostics.stats` has the output. The stats shape depends on which tool was called: #### search ```json { "type": "tool_call", "tool_name": "search", "duration_ms": 156, "diagnostics": { "iteration": 0, "tool_call_id": "call_s1", "arguments": { "query": { "primary": "authentication methods", "variations": ["auth flow", "SSO setup"] }, "retrieval_strategy": "hybrid", "limit": 100, "offset": 0, "filter_groups": [] }, "stats": { "result_count": 47, "new_results": 47, "first_results": [ { "entity_id": "page-auth", "name": "Authentication Overview", "entity_type": "NotionPageEntity", "source_name": "notion", "relevance_score": 0.97 } ] } } } ``` #### read ```json { "type": "tool_call", "tool_name": "read", "duration_ms": 42, "diagnostics": { "iteration": 1, "tool_call_id": "call_r1", "arguments": { "entity_ids": ["page-auth", "page-oauth", "page-sso"] }, "stats": { "found": 3, "not_found": 0, "entities": [ { "entity_id": "page-auth", "name": "Authentication Overview", "entity_type": "NotionPageEntity", "source_name": "notion" } ], "context_label": "3 entities, chunks 0-2" } } } ``` #### add\_to\_results ```json { "type": "tool_call", "tool_name": "add_to_results", "duration_ms": 1, "diagnostics": { "iteration": 1, "tool_call_id": "call_a1", "arguments": { "entity_ids": ["page-auth", "page-oauth"] }, "stats": { "added": 2, "already_collected": 0, "not_found": 0, "total_collected": 2 } } } ``` #### remove\_from\_results ```json { "type": "tool_call", "tool_name": "remove_from_results", "duration_ms": 1, "diagnostics": { "iteration": 3, "tool_call_id": "call_rm1", "arguments": { "entity_ids": ["page-unrelated"] }, "stats": { "added": 0, "already_collected": 0, "not_found": 0, "total_collected": 5 } } } ``` #### count ```json { "type": "tool_call", "tool_name": "count", "duration_ms": 23, "diagnostics": { "iteration": 0, "tool_call_id": "call_c1", "arguments": { "filter_groups": [ { "conditions": [ { "field": "airweave_system_metadata.source_name", "operator": "equals", "value": "github" } ] } ] }, "stats": { "count": 312 } } } ``` #### get\_children ```json { "type": "tool_call", "tool_name": "get_children", "duration_ms": 67, "diagnostics": { "iteration": 2, "tool_call_id": "call_gc1", "arguments": { "entity_id": "chan-engineering", "limit": 50 }, "stats": { "result_count": 34, "context_label": "children of chan-engineering", "first_results": [ { "entity_id": "msg-001", "name": "Auth migration plan", "entity_type": "SlackMessageEntity", "source_name": "slack" } ] } } } ``` #### get\_siblings ```json { "type": "tool_call", "tool_name": "get_siblings", "duration_ms": 55, "diagnostics": { "iteration": 2, "tool_call_id": "call_gs1", "arguments": { "entity_id": "page-oauth", "limit": 50 }, "stats": { "result_count": 8, "context_label": "siblings of page-oauth under db-docs", "first_results": [ { "entity_id": "page-api-keys", "name": "API Key Management", "entity_type": "NotionPageEntity", "source_name": "notion" } ] } } } ``` #### get\_parent ```json { "type": "tool_call", "tool_name": "get_parent", "duration_ms": 18, "diagnostics": { "iteration": 2, "tool_call_id": "call_gp1", "arguments": { "entity_id": "page-oauth" }, "stats": { "result_count": 1, "context_label": "parent of page-oauth", "first_results": [ { "entity_id": "db-docs", "name": "Documentation", "entity_type": "NotionDatabaseEntity", "source_name": "notion" } ] } } } ``` #### review\_results ```json { "type": "tool_call", "tool_name": "review_results", "duration_ms": 2, "diagnostics": { "iteration": 4, "tool_call_id": "call_rr1", "arguments": {}, "stats": { "total_collected": 12, "entity_count": 12, "first_results": [ { "entity_id": "page-auth", "name": "Authentication Overview", "entity_type": "NotionPageEntity", "source_name": "notion", "relevance_score": 0.97 } ] } } } ``` #### return\_results\_to\_user ```json { "type": "tool_call", "tool_name": "return_results_to_user", "duration_ms": 1, "diagnostics": { "iteration": 5, "tool_call_id": "call_ret1", "arguments": {}, "stats": { "accepted": true, "total_collected": 12, "warning": null } } } ``` #### reranking Emitted after the agent's collected results are reranked for final ordering. ```json { "type": "reranking", "duration_ms": 890, "diagnostics": { "input_count": 12, "output_count": 12, "model": "cohere/rerank-v4.0-pro", "top_relevance_score": 0.98, "bottom_relevance_score": 0.41, "first_results": [ { "entity_id": "page-auth", "name": "Authentication Overview", "entity_type": "NotionPageEntity", "source_name": "notion", "relevance_score": 0.98 } ] } } ``` #### done Final event. Contains the full result set and run diagnostics. ```json { "type": "done", "results": ["..."], "duration_ms": 34521, "diagnostics": { "total_iterations": 6, "all_seen_entity_ids": ["page-auth", "page-oauth", "page-sso", "..."], "all_read_entity_ids": ["page-auth", "page-oauth", "page-sso"], "all_collected_entity_ids": ["page-auth", "page-oauth", "pr-auth-456"], "max_iterations_hit": false, "total_llm_retries": 0, "stagnation_nudges_sent": 0, "prompt_tokens": 28450, "completion_tokens": 5230, "cache_creation_input_tokens": 12000, "cache_read_input_tokens": 8500 } } ``` #### error Emitted when the search fails. Also terminates the stream. ```json { "type": "error", "message": "Context window too full for useful work after emergency compression", "duration_ms": 15230 } ``` ## Filters Filters constrain search results by metadata. They work across all three tiers. In classic and agentic search, the AI generates its own filters internally, your filters are **AND'd into every search** it performs, acting as constraints that cannot be bypassed. ### Structure Filters use a two-level structure: * **Conditions** within a group are combined with **AND** * Multiple **groups** are combined with **OR** This allows expressions like: `(A AND B) OR (C AND D)` ```json { "filter": [ { "conditions": [ { "field": "airweave_system_metadata.source_name", "operator": "equals", "value": "slack" }, { "field": "airweave_system_metadata.entity_type", "operator": "equals", "value": "SlackMessageEntity" } ] } ] } ``` ### Filterable Fields | Field | Type | Description | | --------------------------------------------- | ------- | ---------------------------------------- | | `entity_id` | text | Entity identifier | | `name` | text | Entity display name | | `created_at` | date | Creation timestamp | | `updated_at` | date | Last update timestamp | | `breadcrumbs.entity_id` | text | Parent entity ID in the hierarchy | | `breadcrumbs.name` | text | Parent entity name | | `breadcrumbs.entity_type` | text | Parent entity type | | `airweave_system_metadata.entity_type` | text | Entity type (e.g., `SlackMessageEntity`) | | `airweave_system_metadata.source_name` | text | Source name (e.g., `slack`, `notion`) | | `airweave_system_metadata.original_entity_id` | text | Original entity ID (same across chunks) | | `airweave_system_metadata.chunk_index` | numeric | Chunk index for chunked documents | | `airweave_system_metadata.sync_id` | text | Sync ID | | `airweave_system_metadata.sync_job_id` | text | Sync job ID | ### Operators | Operator | Works on | Description | | ----------------------- | ------------- | ----------------------------- | | `equals` | all | Exact match | | `not_equals` | all | Not equal | | `contains` | text only | Substring match | | `greater_than` | date, numeric | `>` | | `less_than` | date, numeric | `<` | | `greater_than_or_equal` | date, numeric | `>=` | | `less_than_or_equal` | date, numeric | `<=` | | `in` | all | Matches any value in the list | | `not_in` | all | Matches none of the values | ### Examples **Filter by source:** ```json { "filter": [ { "conditions": [ { "field": "airweave_system_metadata.source_name", "operator": "equals", "value": "github" } ] } ] } ``` **Filter by time range (ISO 8601 timestamps required):** ```json { "filter": [ { "conditions": [ { "field": "created_at", "operator": "greater_than_or_equal", "value": "2025-01-01T00:00:00Z" }, { "field": "created_at", "operator": "less_than", "value": "2025-02-01T00:00:00Z" } ] } ] } ``` **Filter by multiple sources (using `in`):** ```json { "filter": [ { "conditions": [ { "field": "airweave_system_metadata.source_name", "operator": "in", "value": ["slack", "notion", "github"] } ] } ] } ``` **Combine groups with OR — Slack messages OR Notion pages:** ```json { "filter": [ { "conditions": [ { "field": "airweave_system_metadata.source_name", "operator": "equals", "value": "slack" }, { "field": "airweave_system_metadata.entity_type", "operator": "equals", "value": "SlackMessageEntity" } ] }, { "conditions": [ { "field": "airweave_system_metadata.source_name", "operator": "equals", "value": "notion" }, { "field": "airweave_system_metadata.entity_type", "operator": "equals", "value": "NotionPageEntity" } ] } ] } ``` **Navigate hierarchy — find all entities inside a parent:** ```json { "filter": [ { "conditions": [ { "field": "breadcrumbs.entity_id", "operator": "equals", "value": "parent-entity-id-here" } ] } ] } ``` ### Validation Rules * Date fields (`created_at`, `updated_at`) require ISO 8601 timestamps (e.g., `2025-01-15T00:00:00Z`) * Ordering operators (`greater_than`, `less_than`, etc.) only work on date and numeric fields * `contains` only works on text fields * `in` and `not_in` require array values * Scalar operators (`equals`, `contains`, etc.) require a single value, not an array ## Response Format All three tiers return the same `SearchV2Response` with a `results` array. See the [API Reference](/api-reference/collections/instant-search) for the full response schema and interactive examples. ## Configuring the LLM provider chain > **Self-hosted only** > > This section is only relevant to self-hosted deployments. The managed service ships with providers configured. Classic and Agentic search call an LLM. Instant search does not — a backend with no LLM configured still answers instant queries, and Classic/Agentic return HTTP 503 until an API key is set. ### Default chain Out of the box, Airweave tries providers in this order: 1. `together:zai-glm-5` 2. `anthropic:claude-sonnet-4.6` The first provider with an API key set that responds successfully handles the request. Subsequent entries are tried only on failure. ### Setting API keys Set at least one of the following environment variables on the backend: | Env var | Provider | | ------------------- | --------- | | `TOGETHER_API_KEY` | Together | | `ANTHROPIC_API_KEY` | Anthropic | | `MISTRAL_API_KEY` | Mistral | | `GROQ_API_KEY` | Groq | | `CEREBRAS_API_KEY` | Cerebras | If none are set, the backend boots normally; Classic/Agentic search return `503 Service Unavailable` with a message listing these variables. ### Overriding the chain Set `LLM_FALLBACK_CHAIN` to a comma-separated list of `provider:model` pairs. Example: ``` LLM_FALLBACK_CHAIN=cerebras:gpt-oss-120b,anthropic:claude-sonnet-4.6 ``` Supported providers: `cerebras`, `groq`, `anthropic`, `together`, `mistral`. The full list of models per provider lives in `backend/airweave/adapters/llm/registry.py`. The parser validates three things at startup: * Every provider is a known provider. * Every model is a known model. * Every `(provider, model)` combination exists in the registry (e.g. `together:mistral-large` is rejected because `mistral-large` is hosted on Mistral, not Together). Misconfiguration is caught at startup with an error that lists the accepted values. ### Fallback semantics * Providers without an API key are silently skipped when the chain is built. * Providers whose initialization raises are logged and skipped. * If the resulting chain is empty, the backend wires a null LLM — instant search still works; Classic/Agentic return 503. * When a call fails in a chained provider, the next one is tried; a circuit breaker temporarily removes providers that recently failed. > Search API reference for Airweave