Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Query API

The query engine supports indexed field queries with boolean combinators, pagination, sorting, aggregations, projections, and an explain mode.

Endpoint Summary

MethodPathDescriptionAuthStatus Codes
POST/files/queryExecute a queryYes200, 400, 404, 500
POST/files/searchGlobal cross-directory searchYes200, 400, 500

POST /files/query

Execute a query against indexed fields within a directory path.

Request Body

{
  "path": "/users",
  "where": {
    "field": "age",
    "op": "gt",
    "value": 21
  },
  "limit": 20,
  "offset": 0,
  "order_by": [{"field": "name", "direction": "asc"}],
  "after": null,
  "before": null,
  "include_total": true,
  "select": ["@path", "@score", "name"],
  "include_matches": false,
  "max_matches_per_result": 5,
  "snippet_chars": 160,
  "match_context_lines": 2,
  "max_locator_scan_bytes": 268435456,
  "explain": false
}
FieldTypeRequiredDescription
pathstringYesDirectory path to query within
whereobject/arrayYesQuery filter (see below)
limitintegerNoMax results to return (server default applies if omitted)
offsetintegerNoSkip this many results
order_byarrayNoSort fields with direction
afterstringNoCursor for forward pagination
beforestringNoCursor for backward pagination
include_totalbooleanNoInclude total_count in response (default: false)
selectarrayNoProject specific fields in results
aggregateobjectNoRun aggregations instead of returning results
include_matchesbooleanNoInclude request-time hit locators for returned results (default: false)
max_matches_per_resultintegerNoMaximum hit locators per result (default: 5, max: 50)
snippet_charsintegerNoMaximum snippet characters per locator (default: 160, max: 4096)
match_context_linesintegerNoContext line count for stored-file line fetch hints
max_locator_scan_bytesintegerNoCaller cap for stored-file locator scans; server clamps to its hard cap
explainstring/booleanNo"plan", "analyze", or true for query plan

Hit locators are opt-in. They are generated only for the current page after authorization filtering, not during index lookup.


Query Operators

Each field query is an object with field, op, and value:

{"field": "age", "op": "gt", "value": 21}

Comparison Operators

OperatorDescriptionValue TypeExample
eqExact matchany{"field": "status", "op": "eq", "value": "active"}
gtGreater thannumber/string{"field": "age", "op": "gt", "value": 21}
ltLess thannumber/string{"field": "age", "op": "lt", "value": 65}
betweenInclusive rangenumber/string{"field": "age", "op": "between", "value": 21, "value2": 65}
inMatch any value in a setarray{"field": "status", "op": "in", "value": ["active", "pending"]}

Text Search Operators

These operators require the appropriate index type to be configured.

OperatorDescriptionIndex RequiredExample
containsSubstring matchtrigram{"field": "name", "op": "contains", "value": "alice"}
similarFuzzy trigram match with thresholdtrigram{"field": "name", "op": "similar", "value": "alice", "threshold": 0.3}
phoneticSounds-like matchphonetic{"field": "name", "op": "phonetic", "value": "smith"}
fuzzyConfigurable fuzzy matchtrigramSee below
matchMulti-strategy combined matchtrigram + phonetic{"field": "name", "op": "match", "value": "alice"}

Fuzzy Operator Options

The fuzzy operator supports additional parameters:

{
  "field": "name",
  "op": "fuzzy",
  "value": "alice",
  "fuzziness": "auto",
  "algorithm": "damerau_levenshtein"
}
ParameterValuesDefault
fuzziness"auto" or integer (edit distance)"auto"
algorithm"damerau_levenshtein", "jaro_winkler""damerau_levenshtein"

Similar Operator Options

{
  "field": "name",
  "op": "similar",
  "value": "alice",
  "threshold": 0.3
}
ParameterTypeDefaultDescription
thresholdfloat0.3Minimum similarity score (0.0 to 1.0)

Boolean Combinators

Combine multiple conditions using and, or, and not:

AND

All conditions must match:

{
  "where": {
    "and": [
      {"field": "age", "op": "gt", "value": 21},
      {"field": "status", "op": "eq", "value": "active"}
    ]
  }
}

OR

At least one condition must match:

{
  "where": {
    "or": [
      {"field": "status", "op": "eq", "value": "active"},
      {"field": "status", "op": "eq", "value": "pending"}
    ]
  }
}

NOT

Invert a condition:

{
  "where": {
    "not": {"field": "status", "op": "eq", "value": "deleted"}
  }
}

Nested Boolean Logic

Combinators can be nested up to 32 levels deep. Queries exceeding this depth are rejected with a 400 error. Additionally, a single query can return at most 100,000 results – requests that would exceed this ceiling are truncated.

{
  "where": {
    "and": [
      {"field": "age", "op": "gt", "value": 21},
      {
        "or": [
          {"field": "role", "op": "eq", "value": "admin"},
          {"field": "role", "op": "eq", "value": "moderator"}
        ]
      }
    ]
  }
}

Legacy Array Format

An array at the top level is sugar for AND:

{
  "where": [
    {"field": "age", "op": "gt", "value": 21},
    {"field": "status", "op": "eq", "value": "active"}
  ]
}

Response Format

Standard Query Response

{
  "items": [
    {
      "path": "/users/alice.json",
      "size": 256,
      "content_type": "application/json",
      "created_at": 1775968398000,
      "updated_at": 1775968398000,
      "score": 1.0,
      "matched_by": ["name"]
    }
  ],
  "has_more": true,
  "total_count": 150,
  "next_cursor": "eyJwYXRoIjoiL3VzZXJzL2JvYi5qc29uIn0=",
  "prev_cursor": "eyJwYXRoIjoiL3VzZXJzL2Fhcm9uLmpzb24ifQ==",
  "meta": {
    "reindexing": 0.67,
    "reindexing_eta": 1775968398803,
    "reindexing_indexed": 670,
    "reindexing_total": 1000,
    "reindexing_stale_since": 1775968300000
  }
}
FieldTypeDescription
itemsarrayMatching file metadata with scores
has_morebooleanWhether more results exist beyond the current page
total_countintegerTotal matching results (only if include_total: true)
next_cursorstringCursor for the next page (if has_more is true)
prev_cursorstringCursor for the previous page
default_limit_hitbooleanPresent and true when the server’s default limit was applied
default_limitintegerThe server’s default limit value (present with default_limit_hit)
metaobjectReindex progress metadata (present only during active reindex)

Result Fields

Each result object contains:

FieldTypeDescription
pathstringFull path to the matched file
sizeintegerFile size in bytes
content_typestringMIME type (nullable)
created_atintegerCreation timestamp (ms)
updated_atintegerLast update timestamp (ms)
scorefloatRelevance score (1.0 = exact match)
matched_byarrayList of field names that matched
content_hashstringWhole-file content hash, present when include_matches is true and the record has a hash
matchesarrayHit locators, present when include_matches is true
matches_truncatedbooleanTrue if more locators were available than returned
locator_statusstringcomplete, partial, or unsupported

Hit Locators

When include_matches is true, /files/query and /files/search add bounded hit locators to each returned result. This is designed for agents that need to search first and then fetch only relevant ranges.

For JSON files, matching indexed fields are reported as field-value locators with JSON Pointer fetch hints:

{
  "path": "/users/alice.json",
  "content_hash": "b3c1...",
  "matches": [
    {
      "id": "m_0001",
      "query": "Alice",
      "matched_text": "Alice",
      "field": "name",
      "operator": "eq",
      "source": {
        "type": "field-value",
        "field": "name",
        "json_pointer": "/name",
        "value_type": "string"
      },
      "range": {
        "char": { "start": 0, "end": 5, "unit": "unicode-scalar", "basis": "field-value" }
      },
      "fetch": {
        "json_pointer": "/name",
        "preferred": "json_pointer"
      },
      "snippet": {
        "text": "Alice",
        "highlight": [{ "start": 0, "end": 5, "unit": "unicode-scalar" }],
        "truncated_before": false,
        "truncated_after": false
      },
      "confidence": "exact",
      "scan_status": "complete"
    }
  ],
  "matches_truncated": false,
  "locator_status": "complete"
}

Metadata matches such as @filename use source.type = "metadata". Plain-text or unstructured UTF-8 file matches use source.type = "stored-file" and include byte, character, line, and column ranges plus byte_range and line_range fetch hints.

Use POST /files/fetch range mode to fetch the returned ranges. Pass if_content_hash or if_updated_at from the search result to avoid fetching a stale range after the file changes.


Sorting

Sort results by one or more fields:

{
  "order_by": [
    {"field": "name", "direction": "asc"},
    {"field": "created_at", "direction": "desc"}
  ]
}
DirectionDescription
ascAscending (default)
descDescending

Pagination

Offset-Based

{
  "limit": 20,
  "offset": 40
}

Cursor-Based

Use after or before with cursor values from a previous response:

{
  "limit": 20,
  "after": "eyJwYXRoIjoiL3VzZXJzL2JvYi5qc29uIn0="
}

Projection (select)

Return only specific fields in each result. Use @-prefixed names for built-in metadata fields:

{
  "select": ["@path", "@score", "name", "email"]
}
Virtual FieldMaps To
@pathpath
@scorescore
@sizesize
@content_typecontent_type
@created_atcreated_at
@updated_atupdated_at
@matched_bymatched_by
@content_hashcontent_hash
@matchesmatches

Envelope fields (has_more, next_cursor, total_count, meta) are never stripped by projection.


Aggregations

Run aggregate computations instead of returning individual results.

Request

{
  "path": "/orders",
  "where": {"field": "status", "op": "eq", "value": "complete"},
  "aggregate": {
    "count": true,
    "sum": ["total", "tax"],
    "avg": ["total"],
    "min": ["total"],
    "max": ["total"],
    "group_by": ["status"]
  }
}
FieldTypeDescription
countbooleanInclude a count of matching records
sumarrayFields to sum
avgarrayFields to average
minarrayFields to find minimum
maxarrayFields to find maximum
group_byarrayFields to group results by

Response

The response shape depends on whether group_by is used. Aggregation results are returned as a JSON object.


Explain Mode

Inspect the query execution plan without running the full query. Useful for debugging index usage and performance.

{
  "path": "/users",
  "where": {"field": "age", "op": "gt", "value": 21},
  "explain": "plan"
}
ValueDescription
true or "plan"Show the query plan
"analyze"Execute the query and include timing information

Virtual Fields

Virtual fields let you query file metadata directly. Prefix a field name with @ to query against built-in file attributes instead of indexed document fields.

AeorDB uses virtual-field indexes when they are configured. New databases bootstrap default virtual-field indexes at /.aeordb-config/indexes.json; older databases can add the same config and run a forced reindex to backfill them. If no index exists for a supported virtual field, AeorDB falls back to scanning FileRecord metadata for compatibility.

Available Virtual Fields

FieldTypeDescription
@pathstringFull file path (e.g., /docs/report.pdf)
@filenamestringFilename only – the last segment of the path (e.g., report.pdf)
@file_namestringAlias for @filename; queries use the canonical @filename index
@extensionstringFile extension after the last . (e.g., pdf)
@content_typestringMIME type (e.g., application/pdf)
@hashstringRaw whole-file content hash (blake3(file bytes))
@sizeu64File size in bytes
@created_ati64Creation timestamp in milliseconds
@updated_ati64Last update timestamp in milliseconds

Supported Operators

String virtual fields (@path, @filename, @file_name, @extension, @content_type, @hash) support:

eq, contains, in, gt, lt, similar (trigram), fuzzy (edit distance), phonetic (soundex/metaphone), match (fused multi-strategy)

Numeric virtual fields (@size, @created_at, @updated_at) support:

eq, gt, lt, between, in

How Virtual Fields Work

Virtual fields are derived from the FileRecord header and payload. Query execution is index-first:

  • Default indexes. New databases index every built-in virtual field for exact lookup by default. @filename also gets trigram and phonetic indexes for contains, similar, fuzzy, phonetic, and match.
  • Ancestor glob indexes. If a query is scoped below a glob-indexed directory, AeorDB can use the ancestor index and filter candidates back down to the requested path.
  • Scan fallback. If a database does not have a matching virtual-field index, AeorDB scans FileRecords under the query path so legacy queries continue to work. This fallback is O(n) over files under the path.
  • Combinable with indexed fields. You can mix virtual fields and indexed fields in the same where clause using boolean combinators (and, or, not).
  • Canonical aliases. @file_name is accepted as an alias but is indexed and queried as @filename.

Virtual Field Examples

Find all PDFs by extension:

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/",
    "where": {"field": "@extension", "op": "eq", "value": "pdf"}
  }'

Fuzzy filename search (handles typos):

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/docs",
    "where": {"field": "@filename", "op": "similar", "value": "report", "threshold": 0.3}
  }'

Find large files over 10 MB:

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/",
    "where": {"field": "@size", "op": "gt", "value": 10485760}
  }'

Find images by content type:

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/",
    "where": {"field": "@content_type", "op": "contains", "value": "image/"}
  }'

Combine multiple virtual fields – large PDFs:

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/",
    "where": {
      "and": [
        {"field": "@extension", "op": "eq", "value": "pdf"},
        {"field": "@size", "op": "gt", "value": 1048576}
      ]
    }
  }'

Search across all indexed directories in the database.

POST /files/search

FieldTypeRequiredDescription
querystringNoBroad search — searched against all trigram, phonetic, soundex, and dmetaphone indexed fields
whereobjectNoStructured query filter (same syntax as /files/query)
pathstringNoScope search to a subtree (default: / = everything)
limitintegerNoMax results (default: 50, max: 1000)
offsetintegerNoSkip results
include_matchesbooleanNoInclude request-time hit locators for returned results (default: false)
max_matches_per_resultintegerNoMaximum hit locators per result (default: 5, max: 50)
snippet_charsintegerNoMaximum snippet characters per locator (default: 160, max: 4096)
match_context_linesintegerNoContext line count for stored-file line fetch hints
max_locator_scan_bytesintegerNoCaller cap for stored-file locator scans; server clamps to its hard cap

At least one of query or where is required.

Discovers all directories with fuzzy-capable indexes (trigram, phonetic, soundex, dmetaphone) and searches the term against every matching field:

curl -X POST http://localhost:6830/files/search \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "alice", "limit": 20}'

Same where clause syntax as /files/query, but searches across all directories that have the requested field indexed:

curl -X POST http://localhost:6830/files/search \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"where": {"field": "@size", "op": "gt", "value": 1048576}}'

Combined

Broad search filtered by structured conditions:

curl -X POST http://localhost:6830/files/search \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "report", "where": {"field": "@extension", "op": "eq", "value": "pdf"}}'

Response

Same format as /files/query, plus a source field per result indicating which directory’s index matched. The locator fields below are present only when include_matches is true:

{
  "results": [
    {
      "path": "/users/alice.json",
      "score": 0.95,
      "matched_by": ["@filename"],
      "source": "/",
      "size": 256,
      "content_type": "application/json",
      "created_at": 1775968398000,
      "updated_at": 1775968398000,
      "content_hash": "b3c1...",
      "matches": [],
      "matches_truncated": false,
      "locator_status": "complete"
    }
  ],
  "has_more": false,
  "total_count": 1
}

Examples

Simple equality query

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/users",
    "where": {"field": "status", "op": "eq", "value": "active"},
    "limit": 10
  }'

Fuzzy name search with pagination

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/users",
    "where": {"field": "name", "op": "similar", "value": "alice", "threshold": 0.4},
    "limit": 20,
    "order_by": [{"field": "name", "direction": "asc"}],
    "include_total": true
  }'

Complex boolean query

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/products",
    "where": {
      "and": [
        {"field": "price", "op": "between", "value": 10, "value2": 100},
        {
          "or": [
            {"field": "category", "op": "eq", "value": "electronics"},
            {"field": "category", "op": "eq", "value": "books"}
          ]
        },
        {"not": {"field": "status", "op": "eq", "value": "discontinued"}}
      ]
    },
    "order_by": [{"field": "price", "direction": "asc"}],
    "limit": 50
  }'

Aggregation with grouping

curl -X POST http://localhost:6830/files/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/orders",
    "where": {"field": "year", "op": "eq", "value": 2026},
    "aggregate": {
      "count": true,
      "sum": ["total"],
      "avg": ["total"],
      "group_by": ["status"]
    }
  }'

Error Responses

StatusCondition
400Invalid query structure, missing field/op, unsupported operation, range query on non-range converter
404Query path or index not found
500Internal query execution failure