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.
| Operation | Python | TypeScript | .NET | Go |
|---|---|---|---|---|
| Search for metadata and passages | search | search | SearchAsync | Search |
| Retrieve full content for RAG | retrieve | retrieve | RetrieveAsync | Retrieve |
| Search with your own query vector | search_by_vector | searchByVector | SearchByVectorAsync | SearchByVector |
| Run multiple searches in one call | batch_search | batchSearch | BatchSearchAsync | BatchSearch |
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.
Search
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:
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
| Parameter | Type | Default | Applies to | Notes |
|---|---|---|---|---|
query / q | string | required | search, retrieve, batch query | Natural-language query text. |
k | int | 10 for search, 5 for retrieve | all search methods | Upper bound on returned results. |
tags | string[] | none | search, retrieve, search_by_vector, batch query | AND filter: every listed tag must be present. |
any_tags | string[] | none | search, retrieve, search_by_vector, batch query | OR filter over tags: at least one listed tag must be present. Composes with tags by AND. |
content_types | string[] | none | search, retrieve, search_by_vector, batch query | OR filter: the document's content type must be one of these. |
sources | string[] | none | search, retrieve, search_by_vector, batch query | OR filter: the document's source must be one of these. |
filter | object | none | search, retrieve, search_by_vector, batch query | Structured metadata filter. Keys may be metadata.<key> or bare keys; values use equality shorthand or operators. |
entity_id | string | none | search, retrieve, search_by_vector, batch query | Restrict to one entity (e.g. a user/customer id). |
thread_id / threadId | string | none | search, retrieve, search_by_vector, batch query | Restrict results to one conversational thread. |
include_content / includeContent | bool | false | search, search_by_vector, batch query | Requests full document content inline. retrieve sets this for you. |
embedding | float[] | required | search_by_vector | Pre-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:
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:
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.
Batch search
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.
| Method | Path | Purpose |
|---|---|---|
GET | /v1/search | Search by natural-language query. |
POST | /v1/search/embed | Search by caller-provided embedding vector. |
POST | /v1/search/batch | Run multiple natural-language searches. |
GET /v1/search
| Query parameter | Type | Required | Notes |
|---|---|---|---|
q | string | yes | Natural-language query. |
k | int | no | Defaults to 10. |
include_content | bool | no | Adds content to each result when possible. |
tags | comma-separated string | no | AND filter. Tag values must not contain commas. |
any_tags | comma-separated string | no | OR filter over tags. |
content_type | comma-separated string | no | OR filter over content types. |
source | comma-separated string | no | OR filter over source labels. |
filter | URL-encoded JSON object | no | Structured metadata filter. Values use equality shorthand or eq/ne/gt/lt/gte/lte/in. |
kind | string | no | Filter to one document kind. kind=fact returns only extracted facts; omit for both facts and raw documents. See Fact extraction. |
max_distance | float | no | Advanced distance threshold. Results outside the threshold are dropped before the response is scored. |
recency_weight | float | no | Blend recency into ranking, 0.0–1.0 (default 0.0 = pure similarity). See Recency-weighted ranking. |
half_life_days | float | no | Recency-decay half-life in days (default 30). Applies only when recency_weight > 0; must be > 0. |
freshness_weight | float | no | Blend freshness (time since last update) into ranking, 0.0–1.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_days | float | no | Freshness-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
{
"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
{
"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
{
"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:
{
"results": [
{
"query": "deployment",
"results": []
}
]
}
Errors
Search endpoints use the shared API error shape:
{
"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.