API Reference

Search & Retrieval API

Find relevant documents through the SDK search methods. REST details are included as a contract reference for debugging and advanced integrations.

SDK methods

Most applications should call the SDK. The SDK handles authentication, URL construction, retries, and response parsing.

OperationPythonTypeScript.NETGo
Search for metadata and passagessearchsearchSearchAsyncSearch
Retrieve full content for RAGretrieveretrieveRetrieveAsyncRetrieve
Search with your own query vectorsearch_by_vectorsearchByVectorSearchByVectorAsyncSearchByVector
Run multiple searches in one callbatch_searchbatchSearchBatchSearchAsyncBatchSearch

Search hits expose score: a calibrated relevance integer from 0 to 100, where higher is better. Treat any cutoff as application-specific and validate it against your own documents.

Use search when you need ranked document IDs, titles, content types, and matched passages without downloading full document content. Image captions and audio transcripts share the same ranking space; media hits add a modality field.

results = client.search("deployment best practices", k=10)

for res in results:
    print(res.doc_id, res.score, res.passage)

Each SearchResult has this shape:

TypeScript
interface SearchResult {
  doc_id: string;
  score: number;       // 0-100, higher is more relevant
  title?: string;
  content_type: string;
  thread_id?: string;    // conversation identity when this is a thread turn
  turn_index?: number;   // server-assigned zero-based position in that thread
  tags: string[];         // the document's tags, echoed on every hit
  metadata: Record<string, string | number | boolean>;  // structured metadata, echoed on every hit
  source?: string;        // the document's origin label, when set
  created_at?: string;    // when the document was created (RFC 3339)
  updated_at?: string;    // when the document was last updated (RFC 3339); unset until the first update
  content?: string;       // present only when inline content is requested
  passage?: string;       // matched chunk when available
  modality?: "image" | "audio"; // absent for text
}

Parameters

ParameterTypeDefaultApplies toNotes
query / qstringrequiredsearch, retrieve, batch queryNatural-language query text.
kint10 for search, 5 for retrieveall search methodsUpper bound on returned results.
tagsstring[]nonesearch, retrieve, search_by_vector, batch queryAND filter: every listed tag must be present.
any_tagsstring[]nonesearch, retrieve, search_by_vector, batch queryOR filter over tags: at least one listed tag must be present. Composes with tags by AND.
content_typesstring[]nonesearch, retrieve, search_by_vector, batch queryOR filter: the document's content type must be one of these.
sourcesstring[]nonesearch, retrieve, search_by_vector, batch queryOR filter: the document's source must be one of these.
filterobjectnonesearch, retrieve, search_by_vector, batch queryStructured metadata filter. Keys may be metadata.<key> or bare keys; values use equality shorthand or operators.
entity_idstringnonesearch, retrieve, search_by_vector, batch queryRestrict to one entity (e.g. a user/customer id).
thread_id / threadIdstringnonesearch, retrieve, search_by_vector, batch queryRestrict results to one conversational thread.
include_content / includeContentboolfalsesearch, search_by_vector, batch queryRequests full document content inline. retrieve sets this for you.
embeddingfloat[]requiredsearch_by_vectorPre-computed query vector for BYOE workflows.

Filters compose with AND across facets, OR within a multi-value facet — a hit must satisfy every filter you set, and matches a multi-value filter (any_tags, content_types, sources) if it matches any listed value. Search returns the top k by relevance; when you need the complete set of documents matching a metadata filter (not just the most similar), list them instead — see Documents.

Retrieve for RAG

Use retrieve when you want text to pass to an LLM. It performs search, asks for inline content when the server supports it, deduplicates by doc_id, and falls back to downloading document text if needed.

results = client.retrieve("deployment best practices", k=5)

for res in results:
    print(res.doc_id, res.title, res.score)
    print(res.content[:200])

RetrievalResult is a SearchResult with content guaranteed. For text it is the full document; for image/audio it is the indexed caption/transcript, not binary bytes:

TypeScript
interface RetrievalResult extends SearchResult {
  content: string;
}

Filtering

Partition scoping

For multi-tenant apps, scope a search to a single end-client with a partition. Unlike tags (a post-filter), a partition is a hard boundary the server applies before the search runs, so a scoped query never considers another partition's documents and a selective partition keeps full recall. Scope a client once and search through it — there's no per-call partition argument:

acme = client.partition("client_acme")
results = acme.retrieve("billing preferences", k=10)   # only ever Acme's docs

To prove a scoped search stays in its partition, use search_trace / searchTrace (returns the partitions a query touched) or the one-line verify_isolation / verifyIsolation self-test — see Provable isolation.

Tags and metadata

Tags are simple string facets. Pass tags when inserting documents, then pass the same tags to search or retrieve. A result must match every requested tag.

client.insert_text(
    "Acme prefers invoices in EUR, billed quarterly.",
    filename="acme-billing.txt",
    tags=["customer:acme", "kind:memory"],
)

results = client.retrieve(
    "billing preferences",
    k=10,
    tags=["customer:acme", "kind:memory"],
)

Structured metadata is a typed JSON object attached at insert time. Values must be strings, numbers, or booleans. Query with filter; predicates are ANDed and can use equality shorthand or operator objects: eq, ne, gt, lt, gte, lte, and in.

client.insert_text(
    "Session notes: intake visit, clinical severity 0.8.",
    filename="session-1.txt",
    metadata={
        "severity": 0.8,
        "is_clinical": True,
        "session_type": "intake",
    },
)

results = client.retrieve(
    "recent clinical intake notes",
    k=5,
    filter={
        "metadata.severity": {"gte": 0.7},
        "metadata.is_clinical": True,
        "metadata.session_type": {"in": ["intake", "review"]},
    },
)

Bare filter keys like "severity" also work, but metadata.severity is clearer when a request mixes several facets. Tags remain useful for simple labels and compatibility with existing data; structured metadata is better for typed values, ranges, and exact enum-style fields.

Need exact counts or rankings, not the top matches?

Search returns the most relevant documents. When you need the complete set matching a condition, a total, an average, or results sorted by a typed value — over declared fields with the full operator set (between, contains, numeric and datetime ranges) — use a structured query. It shares this same filter grammar and never runs an embedding.

When a tag matches only a small slice of your documents, a filtered search can return fewer than k results even though more matching documents exist. Request a larger k and filter weak matches by score in your application:

Python
results = client.retrieve("billing preferences", k=10, tags=["customer:acme"])
strong = [r for r in results if r.score >= 60]

The hosted REST API also accepts a max_distance parameter for advanced distance-threshold filtering before scores are returned. Prefer SDK-level over-retrieval plus client-side score filtering unless you are deliberately working at the REST contract layer.

Recency- and freshness-weighted ranking

To bias results toward recent documents without dropping older ones, pass recency_weight (and optionally half_life_days) to blend an exponential recency score into the ranking server-side. Agent-memory apps get this through the Memory facade's recall(recency_weight=...). See Recency-weighted ranking for the formula, a decay-by-age table, and a worked example.

To boost recently updated documents instead, pass freshness_weight (and optionally freshness_half_life_days, default 14). Freshness is keyed to updated_at, falling back to created_at for documents that have never been updated, and composes with recency: recency_weight + freshness_weight must not exceed 1.0, or the request returns 400. Freshness ranking may require a Scale plan or higher. See Freshness: boosting recently updated documents.

Use batch search when you have several independent queries and want one network round trip.

from aether import BatchSearchQuery

responses = client.batch_search([
    BatchSearchQuery(q="deployment", k=3),
    BatchSearchQuery(q="billing preferences", k=3),
])

for response in responses:
    print(response.query, [hit.doc_id for hit in response.results])

Batch responses are returned in the same order as the input queries. Batch queries accept the same tag, entity, time, and structured metadata filters as single searches.

Search by vector

Use search_by_vector / searchByVector / SearchByVectorAsync / SearchByVector when you generate the query embedding yourself.

results = client.search_by_vector([0.1, 0.2, 0.3, ...], k=5)

for res in results:
    print(res.doc_id, res.score)

Your vector length must match the active embedding index. The default hosted configuration uses minilm-l6-v2; the node detects the model output dimension and defaults to 384 dimensions for the MiniLM path. A mismatched vector returns 400 Bad Request.

REST contract

The SDKs call these routes internally. Use them directly only for debugging, custom clients, or advanced integrations that cannot use an SDK. Routes are versioned under the /v1 prefix; unversioned paths are deprecated aliases — see API versioning.

MethodPathPurpose
GET/v1/searchSearch by natural-language query.
POST/v1/search/embedSearch by caller-provided embedding vector.
POST/v1/search/batchRun multiple natural-language searches.

GET /v1/search

Query parameterTypeRequiredNotes
qstringyesNatural-language query.
kintnoDefaults to 10.
include_contentboolnoAdds content to each result when possible.
tagscomma-separated stringnoAND filter. Tag values must not contain commas.
any_tagscomma-separated stringnoOR filter over tags.
content_typecomma-separated stringnoOR filter over content types.
sourcecomma-separated stringnoOR filter over source labels.
filterURL-encoded JSON objectnoStructured metadata filter. Values use equality shorthand or eq/ne/gt/lt/gte/lte/in.
kindstringnoFilter to one document kind. kind=fact returns only extracted facts; omit for both facts and raw documents. See Fact extraction.
max_distancefloatnoAdvanced distance threshold. Results outside the threshold are dropped before the response is scored.
recency_weightfloatnoBlend recency into ranking, 0.01.0 (default 0.0 = pure similarity). See Recency-weighted ranking.
half_life_daysfloatnoRecency-decay half-life in days (default 30). Applies only when recency_weight > 0; must be > 0.
freshness_weightfloatnoBlend freshness (time since last update) into ranking, 0.01.0 (default 0.0 = off). Keyed to updated_at, falling back to created_at for never-updated documents. May require a Scale plan or higher. See Freshness.
freshness_half_life_daysfloatnoFreshness-decay half-life in days (default 14). Applies only when freshness_weight > 0; must be > 0.

All four ranking parameters — recency_weight, half_life_days, freshness_weight, and freshness_half_life_days — are also accepted by POST /v1/search/embed and each POST /v1/search/batch query. An out-of-range weight or a non-positive half-life returns 400, and the two weights must not sum past 1.0: a request with recency_weight + freshness_weight > 1.0 also returns 400.

POST /v1/search/embed

JSON
{
  "embedding": [0.1, 0.2, 0.3],
  "k": 5,
  "include_content": false,
  "tags": ["customer:acme"],
  "filter": {
    "metadata.severity": { "gte": 0.7 }
  },
  "max_distance": 0.4,
  "recency_weight": 0.3,
  "half_life_days": 30,
  "freshness_weight": 0.3,
  "freshness_half_life_days": 14
}

POST /v1/search/batch

JSON
{
  "queries": [
    {
      "q": "deployment",
      "k": 3,
      "include_content": false,
      "tags": "customer:acme",
      "filter": {
        "metadata.session_type": { "in": ["intake", "review"] }
      },
      "max_distance": 0.4,
      "recency_weight": 0.3,
      "half_life_days": 30,
      "freshness_weight": 0.3,
      "freshness_half_life_days": 14
    }
  ]
}

In the REST batch body, tags, any_tags, content_type, and source are comma-separated strings per query. SDK batch models expose arrays and encode them for you. Structured filter remains a JSON object.

Response shape

JSON
{
  "query": "deployment",
  "results": [
    {
      "doc_id": "doc_123",
      "score": 87,
      "title": "Production setup",
      "content_type": "text/plain",
      "passage": "Deploy from a protected branch...",
      "content": "Deploy from a protected branch..."
    }
  ]
}

Batch search wraps one response per query:

JSON
{
  "results": [
    {
      "query": "deployment",
      "results": []
    }
  ]
}

Errors

Search endpoints use the shared API error shape:

JSON
{
  "error": "Embedding dimension mismatch: got 3, expected 384",
  "code": null,
  "request_id": "req_..."
}

Common statuses are 400 for invalid input, 401 for missing or invalid authentication, 402 for plan limits, 429 for rate limits, and 500 / 503 for transient server errors. See Errors for retry guidance.