Reference
Search API
Run semantic and keyword search across your workspace memories. The search endpoint combines pgvector cosine similarity with keyword frequency scoring and ranks results using a weighted formula. Use it to surface relevant past decisions, patterns, and context before starting new work.
See Memory Concepts for how memories are structured, or Knowledge Base Ingestion cookbook for bulk ingestion patterns.
Base URL and Authentication
https://api.neuroloom.devAll requests require an API key:
Authorization: Token $MEMORIES_API_TOKENSearch Memories
POST /api/v1/memories/searchCombined keyword and semantic search. Results are ranked by a weighted formula and returned ordered by score descending.
Ranking Formula
score = (keyword_score × 0.3) + (semantic_score × 0.6) + (importance_score × 0.1)Semantic search uses pgvector cosine similarity on precomputed embeddings. It requires an embedding provider API key to be configured — if no embedding provider API key is configured, the endpoint falls back to keyword-only ranking.
memory_search(
query="database connection pooling strategy",
memory_types=["pattern", "decision"],
min_importance=0.6,
limit=10
)The MCP memory_search tool accepts one additional parameter not available in the REST API:
| Parameter | Type | Default | Description |
|---|---|---|---|
tag_prefixes | array of strings | — | Filter to memories whose tags start with any of these prefixes. Useful for namespaced tagging schemes. |
The memory_by_file MCP tool is a convenience wrapper that calls search with a files filter:
memory_by_file(
file_path="api/neuroloom_api/routers/memories.py",
limit=10
)import httpx, os
response = httpx.post(
"https://api.neuroloom.dev/api/v1/memories/search",
headers={"Authorization": f"Token {os.environ['MEMORIES_API_TOKEN']}"},
json={
"query": "database connection pooling strategy",
"memory_types": ["pattern", "decision"],
"min_importance": 0.6,
"limit": 10,
"use_semantic": True,
},
)
results = response.json()
for r in results["results"]:
print(r["memory"]["title"], r["score"])curl -X POST "https://api.neuroloom.dev/api/v1/memories/search" \
-H "Authorization: Token $MEMORIES_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "database connection pooling strategy",
"memory_types": ["pattern", "decision"],
"min_importance": 0.6,
"limit": 10
}'Request Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | No | "" | Free-text query used for both keyword matching and semantic similarity. Can be a question, phrase, or concept label. |
memory_types | array of strings | No | null | Filter to one or more memory type values (e.g. ["decision", "pattern"]). Note: plural field name, unlike the list endpoint's singular memory_type. |
tags | array of strings | No | null | Filter to memories that include ANY of these tags (OR semantics), applied only within the keyword/semantic results a non-empty query already produced. See callout below. |
files | array of strings | No | null | Filter to memories that reference ANY of these file paths. Note: this field is files, not source_files. |
min_importance | float | No | null | Only return memories with importance_score >= this value. |
min_confidence | float | No | null | Only return memories with confidence_score >= this value. |
use_semantic | boolean | No | true | Enable semantic (embedding) search. Set false for pure keyword search. |
limit | integer | No | 20 | Max results to return (min 1, max 200). |
offset | integer | No | 0 | Results to skip for pagination. |
The search endpoint uses memory_types (plural array) while the list endpoint uses memory_type (singular string). Using the wrong one silently drops your filter.
File filtering uses files (not source_files). Using source_files in a search request body is silently ignored.
tags narrows results found by keyword or semantic matching — it does not surface a memory on tag match alone. A memory with a matching tag but no matching query text and no semantic-similarity match will not appear. The tag-blind graph lane is the one exception: a memory can appear via a graph match even without the requested tags, since graph matching does not check tags at all. There is currently no MCP tool for a pure tag lookup; for an exact-tag hard filter (AND semantics, no relevance gate), use GET /memories instead.
Response
200 OK
{
"count": 2,
"query": "database connection pooling strategy",
"filters": {
"workspace_id": "ws-xyz789",
"memory_types": ["pattern", "decision"],
"tags": null,
"min_importance": 0.6
},
"results": [
{
"memory": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"workspace_id": "ws-xyz789",
"title": "Use connection pooling for all DB connections",
"narrative": "Configure SQLAlchemy with pool_size=10, max_overflow=20. Never create engines per-request — share a single engine per process.",
"memory_type": "pattern",
"tags": ["sqlalchemy", "database", "performance"],
"concepts": ["connection-pooling", "database"],
"source_files": ["api/neuroloom_api/database.py"],
"importance_score": 0.88,
"confidence_score": 0.9,
"content_hash": null,
"pagerank_score": 0.45,
"community_label": "database-patterns",
"access_count": 8,
"retrieval_count": 22,
"created_at": "2026-03-15T10:00:00Z"
},
"score": 0.92,
"keyword_score": 0.0,
"semantic_score": 0.0,
"match_type": "combined"
}
],
"degraded": false,
"degradation_reason": null,
"code_graph_available": true
}Top-level response fields
| Field | Type | Description |
|---|---|---|
count | integer | Number of results returned on this page, not a total record count. Search is bounded by limit. There is no total_count or cursor field. |
degraded | boolean | true when the query-embedding step was skipped (timeout or an open circuit breaker on the embedding provider). This is a deliberate resilience behavior, not an error state — semantic and graph matching are skipped and results fall back to keyword-only (FTS) with a normal 200 OK, rather than the request failing outright. |
degradation_reason | string | null | Set only when degraded is true. "embed_timeout" — the embedding call exceeded its timeout. "embed_circuit_open" — the embedding provider's circuit breaker is open after prior failures. Retrying later may restore semantic ranking. |
code_graph_available | boolean | null | true when the workspace has code symbols matching the query (graph-derived candidates were available for this search). false when no matching code symbols were found. null when the code-graph check timed out or was skipped. |
SearchResult fields
| Field | Type | Description |
|---|---|---|
memory | object | Full memory object. |
score | float | Combined ranking score, 0.0–1.0. |
keyword_score | float | Reserved for future use; currently always 0.0. |
semantic_score | float | Reserved for future use; currently always 0.0. |
match_type | string | Which component(s) contributed: "keyword", "semantic", or "combined". |
Common Search Patterns
Find memories by file
Surface decisions and patterns captured while working on a specific file:
curl -X POST "https://api.neuroloom.dev/api/v1/memories/search" \
-H "Authorization: Token $MEMORIES_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"files": ["api/neuroloom_api/routers/memories.py"],
"limit": 20
}'Keyword-only search
Disable semantic ranking for exact phrase matching:
curl -X POST "https://api.neuroloom.dev/api/v1/memories/search" \
-H "Authorization: Token $MEMORIES_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "HNSW ef_construction",
"use_semantic": false,
"limit": 10
}'High-importance decisions only
Retrieve only well-established decisions above an importance threshold:
curl -X POST "https://api.neuroloom.dev/api/v1/memories/search" \
-H "Authorization: Token $MEMORIES_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "database schema migration strategy",
"memory_types": ["decision", "architecture"],
"min_importance": 0.8,
"limit": 5
}'Filter by tags
Pair tags with a real query — an empty or omitted query returns no keyword or semantic candidates for tags to narrow. The graph lane is skipped too on an empty query, since it needs a query embedding that an empty query never produces. So a tags-only request without a query does not behave as a tag filter — it returns nothing.
Narrow a search to memories tagged with specific labels:
curl -X POST "https://api.neuroloom.dev/api/v1/memories/search" \
-H "Authorization: Token $MEMORIES_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "HNSW index tuning",
"tags": ["pgvector", "performance"],
"limit": 20
}'Semantic Search Fallback
This is different from the degraded / degradation_reason response fields — this section covers missing/not-yet-generated embeddings (see Response above for the transient-failure case).
When semantic search is enabled (use_semantic: true) but embeddings are unavailable — either because no embedding provider API key is configured or because a memory's embedding has not yet been generated — the endpoint falls back to keyword-only ranking. Results are still returned; only the scoring changes.
To check whether a memory has an embedding, inspect whether pagerank_score is non-null (both are populated by the same background job). Embeddings are generated asynchronously after memory creation, typically within seconds.
Error Responses
| Status | When |
|---|---|
401 Unauthorized | Missing or invalid Authorization header. |
422 Unprocessable Entity | Invalid parameter value (e.g. limit > 200, min_importance > 1.0). |
{
"detail": [
{
"loc": ["body", "limit"],
"msg": "Input should be less than or equal to 200",
"type": "less_than_equal"
}
]
}Related
- What is a Memory — memory types, fields, and data model
- Memory Lifecycle — how importance scores affect search ranking
- Knowledge Base Ingestion — bulk ingestion patterns for semantic search