Skip to main content

Query API

Every node exposes the same HTTP API. Send a query to any of them; the node you reach plans the fan-out.

POST /v1/search
Content-Type: application/json

A minimal query

curl -s http://localhost:7420/v1/search \
-H 'content-type: application/json' \
-d '{"query": {"match": {"text": "satellite imagery"}}}'

Full request

{
"query": {
"bool": {
"must": [
{"match": {"title": "glacier retreat"}}
],
"filter": [
{"range": {"captured_at": {"gte": "2024-01-01"}}},
{"geo_within": {"footprint": {"bbox": [-124.7, 45.5, -120.1, 49.0]}}}
]
}
},
"scope": {
"network": "acme-internal",
"indexes": ["imagery"],
"max_nodes": 64,
"deadline_ms": 750,
"min_coverage": 0.9,
"trust": "verified"
},
"limit": 25,
"explain": true,
"verify": true
}

query

ClausePurpose
matchAnalyzed full-text match
termExact keyword match
rangeNumeric or datetime bounds
geo_withinBounding box or polygon containment
knnVector similarity over an embedded field
boolmust / should / must_not / filter composition

scope

FieldDefaultPurpose
networkjoined networkWhich network to fan out across
indexesallRestrict to named indexes
max_nodes64Fan-out ceiling
deadline_ms750Abandon stragglers past this
min_coverage0Fail the request below this answered fraction
trustverifiedverified, pinned-only, any

Response

{
"hits": [
{
"score": 12.41,
"node": "n1qh7f2m9x8p4vd6kkzq3r7wjn5tbc2a8",
"index": "imagery",
"doc": {
"title": "Puget Sound, cloud-free, 2024-06-11",
"captured_at": "2024-06-11T18:42:00Z",
"sensor": "sentinel-2"
},
"signature": "ed25519:41ab…77c9"
}
],
"coverage": {
"eligible": 41,
"asked": 41,
"answered": 39,
"timed_out": 2,
"refused": 0,
"complete": false
},
"took_ms": 128,
"verified": true
}
Always read coverage

hits alone cannot tell you whether you saw everything. A partial network answer is a normal, expected outcome — coverage.complete is how you find out.

Streaming

Ask for NDJSON and hits arrive as nodes answer, rather than after the slowest one.

curl -N http://localhost:7420/v1/search \
-H 'content-type: application/json' \
-H 'accept: application/x-ndjson' \
-d '{"query": {"match": {"text": "glacier"}}, "scope": {"deadline_ms": 2000}}'
{"type":"plan","eligible":41,"asked":41}
{"type":"hit","score":12.41,"node":"n1qh7f…c2a8","doc":{…}}
{"type":"hit","score":11.87,"node":"n1m4kd…91b3","doc":{…}}
{"type":"coverage","answered":39,"timed_out":2,"complete":false}
{"type":"done","took_ms":128}

Other endpoints

MethodPathPurpose
GET/v1/healthLiveness and readiness
GET/v1/statusPeers, indexes, query statistics
GET/v1/manifestsManifests this node knows about
GET/v1/manifests/{index}This node's own manifest
POST/v1/explainPlan a query without executing it

Errors

{
"error": {
"code": "coverage_below_minimum",
"message": "answered 0.71 of eligible nodes, min_coverage was 0.90",
"coverage": {"eligible": 41, "answered": 29, "complete": false}
}
}
CodeHTTPMeaning
bad_query400Malformed or unresolvable clause
refused_by_policy403The node will not answer you
coverage_below_minimum409Too few nodes answered
verification_failed422A result signature did not check out
deadline_exceeded504No node answered in time