API Reference
Structured Query API
Filter, sort, and aggregate documents by their typed fields — exact and deterministic, with no embedding involved. For a task-oriented walkthrough, start with the Structured queries guide; this page is the parameter and response reference.
SDK methods
| Operation | Python | TypeScript | .NET | Go |
|---|---|---|---|---|
| Run a structured query | query | query | parity follow-up | parity follow-up |
| Declare typed fields | schema.declare_fields | schema.declareFields | parity follow-up | parity follow-up |
| List declared fields | schema.list_fields | schema.listFields | parity follow-up | parity follow-up |
| Delete a field | schema.delete_field | schema.deleteField | parity follow-up | parity follow-up |
The query and field-schema surface ships in the Python and TypeScript SDKs first; .NET and Go parity follow.
Field schema
Declared fields are typed, indexed views over values your documents already carry. See declaring fields for the type and source tables.
Declare fields
schema.declare_fields(fields) → PUT /v1/schema/fields. Body is { "fields": [ { name, type, source, partition_scope? } ] }. Re-declaring a name replaces its definition and re-backfills. Returns the declared set.
fields = client.schema.declare_fields([
{"name": "amount", "type": "float", "source": {"metadata": "amount"}},
{"name": "status", "type": "string", "source": {"metadata": "status"}},
])
List fields
schema.list_fields() → GET /v1/schema/fields. Returns each field with live coverage stats.
Each FieldSchema has this shape:
interface FieldSchema {
name: string;
type: "string" | "int" | "float" | "bool" | "datetime" | "string_list";
source: { metadata: string } | { regex: string };
partition_scope: string | null;
coverage: number; // documents with a value for this field
mismatch_count: number; // documents whose source value couldn't be coerced
backfill: string; // backfill progress for the field
}
Delete a field
schema.delete_field(name) → DELETE /v1/schema/fields/{name}. Returns the remaining fields.
On a partition-scoped handle, all three calls are scoped to that partition automatically.
Query
query(...) → POST /v1/query. Presence of aggregate selects the mode.
Request
| Parameter | Type | Applies to | Notes |
|---|---|---|---|
filter | filter object | both | The unified filter grammar. Omit to match every document in scope. |
sort | [{ by, dir }] | both | dir is asc (default) or desc. Mode A sorts documents (missing values last); Mode B sorts groups by an aggregate output or group key. |
limit | integer | both | Mode A: page size, max 1000. Mode B: maximum number of groups. |
offset | integer | Mode A | Page offset; default 0. |
group_by | [string] | Mode B | Up to two fields; one result row per distinct combination. |
aggregate | [{ op, field?, as? }] | Mode B | Presence switches to aggregation mode. |
partition | string | both | Usually set for you by a partition-scoped handle. |
Filter grammar — { and | or | not } combinators over { field, op, value } leaves; operators eq, neq, in, gt, gte, lt, lte, between, exists, contains, prefix. Full semantics in the guide.
Aggregate operators — count, count_distinct, sum, avg, min, max. The numeric operators require an int or float field.
Response — Mode A (no aggregate)
A document page:
{
"documents": [ /* document records, newest first unless sorted */ ],
"total": 128,
"has_more": true
}
total is the full matching count (not just the returned page); page with offset += limit while has_more is true.
Response — Mode B (aggregate present)
Grouped aggregate rows:
{
"groups": [
{ "keys": { "region": "us-east" }, "aggregates": { "total": 4210.0, "count": 37 } }
],
"total_groups": 4,
"scanned": 128
}
total_groups is the number of groups; scanned is how many documents were considered. A sum over an int field stays an integer; sum/avg otherwise accumulate as floating point.
Examples
# Mode A — filtered, sorted, paged documents
page = client.query(
filter={"field": "status", "op": "eq", "value": "paid"},
sort=[{"by": "amount", "dir": "desc"}],
limit=20,
)
# Mode B — totals per region
result = client.query(
group_by=["region"],
aggregate=[{"op": "sum", "field": "amount", "as": "total"}],
sort=[{"by": "total", "dir": "desc"}],
)
Errors
Structured queries fail loud — the guardrails return 400 (invalid_input) with a precise message rather than a truncated 200:
| Cause | Behavior |
|---|---|
| Unknown field (not declared, not a built-in) | 400 |
Type-mismatched literal (float field vs "cheap") | 400 |
Non-numeric aggregate (sum over a string field) | 400 |
| Group cap or candidate-scan cap exceeded | 400 — narrow the filter or add a partition |
See the error reference for the full error shape.
REST reference
The SDK is the recommended path. For debugging or non-SDK integrations, call the endpoint directly against https://api.aetherdb.ai:
curl -X POST https://api.aetherdb.ai/v1/query \
-H "Authorization: Bearer $AETHER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter": { "field": "status", "op": "eq", "value": "paid" },
"sort": [{ "by": "amount", "dir": "desc" }],
"limit": 20
}'
The field-schema routes take the same bearer auth: PUT /v1/schema/fields, GET /v1/schema/fields, and DELETE /v1/schema/fields/{name}.