Search API
Base path /api/search — JWT required. Searches the calling user's own sources across all their projects (not the public registry — see Query the Registry for that).
GET /api/search
| Param | Required | Purpose |
|---|---|---|
q | yes | Query text. An empty/missing q returns { results: [], total: 0, mode } with no error. |
mode | no | text (default) · semantic · hybrid |
limit | no | 1–50, default 20 |
Modes
text— PostgreSQLpg_trgmtrigram similarity +ILIKEover title/summary/tags. No embeddings needed; always available.semantic—pgvectorcosine similarity against an embedding of the query (sametext-embedding-3-smallmodel used to embed sources, so vectors are comparable), read through an HNSW index.hybrid— runs text and semantic in parallel and fuses the two ranked lists with Reciprocal Rank Fusion (k=60).
Every result is scoped to req.userId — a source is only returned if it belongs to a project the caller owns.
Degradation behavior
semantic and hybrid need a query embedding, which requires OPENAI_API_KEY to be configured server-side:
- If the key is absent:
mode=semantic→422{ error: 'OPENAI_API_KEY not configured — semantic search is unavailable.', mode }mode=hybrid→ falls back totext, withnote: 'Fell back to text search (OPENAI_API_KEY not configured).'
- If the key is present but embedding the query fails:
mode=semantic→502{ error: 'Query embedding failed: <reason>', mode }mode=hybrid→ falls back totext, withnote: 'Fell back to text search (query embedding failed).'
mode=semanticwith no embedded sources yet returns an empty result set withnote: 'No embedded sources matched yet — embeddings are generated in the background after research.'
Response shape
json
{
"results": [
{
"id": "src_123",
"title": "…",
"summary": "…",
"url": "https://…",
"source_type": "research_paper",
"source_domain": "health",
"api_source": "pubmed",
"evidence_grade": "moderate",
"credibility_score": 7,
"tags": ["coq10", "statins"],
"_pname": "My Project",
"_pid": "proj_123",
"_pdomain": "health",
"_score": 0.83
}
],
"total": 1,
"mode": "text"
}_score means different things per mode: trigram similarity (text), cosine similarity (semantic), or the fused RRF score (hybrid) — it's for ranking, not a normalized confidence value.
→ Related: Sources · Query the Registry