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/queryExecute a queryYes200, 400, 404, 500

POST /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"],
  "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
explainstring/booleanNo"plan", "analyze", or true for query plan

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

{
  "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

{
  "results": [
    {
      "path": "/users/alice.json",
      "total_size": 256,
      "content_type": "application/json",
      "created_at": 1775968398000,
      "updated_at": 1775968398000,
      "score": 1.0,
      "matched_by": ["age"]
    }
  ],
  "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
resultsarrayMatching 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
total_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

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
@sizetotal_size
@content_typecontent_type
@created_atcreated_at
@updated_atupdated_at
@matched_bymatched_by

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

Examples

Simple equality query

curl -X POST http://localhost:3000/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:3000/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:3000/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:3000/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