Neuroloom

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.dev

All requests require an API key:

Authorization: Token $MEMORIES_API_TOKEN

Search Memories

POST /api/v1/memories/search

Combined 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:

ParameterTypeDefaultDescription
tag_prefixesarray of stringsFilter 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

FieldTypeRequiredDefaultDescription
querystringNo""Free-text query used for both keyword matching and semantic similarity. Can be a question, phrase, or concept label.
memory_typesarray of stringsNonullFilter to one or more memory type values (e.g. ["decision", "pattern"]). Note: plural field name, unlike the list endpoint's singular memory_type.
tagsarray of stringsNonullFilter 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.
filesarray of stringsNonullFilter to memories that reference ANY of these file paths. Note: this field is files, not source_files.
min_importancefloatNonullOnly return memories with importance_score >= this value.
min_confidencefloatNonullOnly return memories with confidence_score >= this value.
use_semanticbooleanNotrueEnable semantic (embedding) search. Set false for pure keyword search.
limitintegerNo20Max results to return (min 1, max 200).
offsetintegerNo0Results to skip for pagination.
Warning

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.

Warning

File filtering uses files (not source_files). Using source_files in a search request body is silently ignored.

Warning

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

FieldTypeDescription
countintegerNumber of results returned on this page, not a total record count. Search is bounded by limit. There is no total_count or cursor field.
degradedbooleantrue 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_reasonstring | nullSet 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_availableboolean | nulltrue 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

FieldTypeDescription
memoryobjectFull memory object.
scorefloatCombined ranking score, 0.01.0.
keyword_scorefloatReserved for future use; currently always 0.0.
semantic_scorefloatReserved for future use; currently always 0.0.
match_typestringWhich 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
  }'

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

Tip

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

Note

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

StatusWhen
401 UnauthorizedMissing or invalid Authorization header.
422 Unprocessable EntityInvalid 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"
    }
  ]
}

Ready to get started?

Start building with Neuroloom for free.

Start Building Free