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
| Clause | Purpose |
|---|---|
match | Analyzed full-text match |
term | Exact keyword match |
range | Numeric or datetime bounds |
geo_within | Bounding box or polygon containment |
knn | Vector similarity over an embedded field |
bool | must / should / must_not / filter composition |
scope
| Field | Default | Purpose |
|---|---|---|
network | joined network | Which network to fan out across |
indexes | all | Restrict to named indexes |
max_nodes | 64 | Fan-out ceiling |
deadline_ms | 750 | Abandon stragglers past this |
min_coverage | 0 | Fail the request below this answered fraction |
trust | verified | verified, 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
coveragehits 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
| Method | Path | Purpose |
|---|---|---|
GET | /v1/health | Liveness and readiness |
GET | /v1/status | Peers, indexes, query statistics |
GET | /v1/manifests | Manifests this node knows about |
GET | /v1/manifests/{index} | This node's own manifest |
POST | /v1/explain | Plan 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}
}
}
| Code | HTTP | Meaning |
|---|---|---|
bad_query | 400 | Malformed or unresolvable clause |
refused_by_policy | 403 | The node will not answer you |
coverage_below_minimum | 409 | Too few nodes answered |
verification_failed | 422 | A result signature did not check out |
deadline_exceeded | 504 | No node answered in time |