Search
Semantic search across your workspace's documents using vector similarity (pgvector + Gemini embeddings). Search operates at the section-chunk level: it returns the most relevant passages of your documents along with their heading breadcrumbs, plus a single pre-formatted text block ready to drop into an LLM prompt.
Endpoint
GET /api/v1/search?q=<query>
Query parameters
| Name | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
q | string | Yes | — | 1–500 chars | Natural language search query |
limit | number | No | 8 | 1–20 | Maximum chunks to return |
document_id | string (UUID) | No | — | — | Restrict the search to a single document |
budget_tokens | number | No | 1500 | 100–8000 | Token budget for the formatted text block |
There is no threshold parameter — results are ranked by relevance and capped at limit.
Example
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://app.sophic.so/api/v1/search?q=how+do+we+deploy+to+production&limit=8"
Response
The response has two top-level fields:
text— a single string that formats the matched passages (with heading breadcrumbs) for direct LLM consumption, truncated tobudget_tokens. This is what the MCPsearch_knowledge_basetool surfaces to agents.results— the structured matches, ordered by relevance.
{
"text": "## Production Deployment Runbook › Steps\n1. Run migrations...\n\n...",
"results": [
{
"chunk": {
"id": "f1e2...",
"content": "1. Run migrations against the primary...",
"section_id": "a9c0...",
"chunk_index": 0,
"distance": 0.18
},
"section": {
"id": "a9c0...",
"document_id": "abc123",
"heading_title": "Steps",
"heading_depth": 2,
"path": "1.3"
},
"ancestors": [
{ "id": "...", "node_id": "...", "heading_title": "Production Deployment Runbook", "heading_depth": 1 }
]
}
]
}
Each result is a section chunk:
| Field | Description |
|---|---|
chunk.content | The matched passage text |
chunk.distance | Cosine distance from the vector lane. null when the chunk surfaced only via the lexical lane (hybrid mode) |
chunk.rrf_score | Fused reciprocal-rank score — present in hybrid mode only |
section.document_id | The document this chunk belongs to — pass it to GET /api/v1/documents/:id for the full document |
section.heading_title / heading_depth / path | The heading the chunk lives under, and its position in the document outline |
ancestors | The chain of parent headings above the chunk, for breadcrumb context |
How it works
- Your query is embedded using Google Gemini (
gemini-embedding-001, 768 dimensions) - The embedding is matched against document-section chunk vectors via the
match_document_section_chunksPostgres RPC (pgvector cosine similarity), scoped to the workspace - Ancestor headings are joined server-side so each chunk carries its outline context
- The matches are formatted into the
textblock within thebudget_tokensbudget - If embedding generation or retrieval fails, the API returns
500with{ "error": "Search failed" }
Related
GET /api/v1/grep— exact-string / regex search when you have a literal identifier rather than a concept.