The Pizza REST APIs are exposed over HTTP. Except where noted, the following conventions apply across all APIs.
API conventions #
Authentication #
When security.auth_enabled: true is set in pizza.yml, every data-plane
and management request must carry a valid API key — either as
Authorization: Bearer <key> or X-Api-Key: <key>. Unauthenticated
requests are answered with 401 and a deliberately generic body that
does not reveal whether the key was missing or wrong. Anonymous
operational-plane reads (console assets, readiness probes) stay open, but
mutating cluster/node management under /_cluster and /_node requires
authentication once auth is on. See
Security for key management.
Error responses #
Errors share one envelope — an HTTP status code plus a JSON body with the
status, and an error object carrying a machine-readable type and a
human-readable reason:
{
"status": 404,
"error": {
"type": "illegal_argument_exception",
"reason": "collection 'default:foo' not found"
}
}
Startup gate #
While a node is still starting (metadata not applied, shards not
recovered), API requests are answered with 503 and a body whose phase
field names the startup stage currently in progress. Retry with backoff
until the node reports ready.
Request tracing #
Every response echoes the caller’s sanitized X-Request-Id header when
present, and otherwise carries a generated request id — use it to
correlate a response with the node’s logs.
Canonical query syntax and Elasticsearch tolerance #
Pizza’s documented syntax is the canonical (strict) form: leaf queries
carry explicit field and value/query keys, and sorts use the full
form. All examples in this documentation use it.
The gateway is a strict gate on request shape: after translating the
ES forms below, any top-level search-body key the engine’s query model
does not define (e.g. suggest, script, indices_boost) is rejected
with HTTP 400 naming the offending key — never silently ignored, so a
typo or an unsupported feature surfaces immediately instead of
degrading the search. The engine itself stays tolerant by design; the
whitelist lives in the gateway.
graph LR
C["canonical body"] --> G("gateway")
E["Elasticsearch shorthand
(term, sort, knn, retriever)"] -->|"translated — idempotent"| G
G -->|"unknown top-level key"| X["HTTP 400 — names the offending key,
never silently ignored"]
G --> N("engine — one canonical query model")
As a migration courtesy, the gateway also accepts common
Elasticsearch shorthands — e.g. a term leaf may be written
{"term": {"field": "f", "value": "v"}} (Pizza) or
{"term": {"f": "v"}} (ES), and sorts accept both
{"field": "f", "order": "asc"} (Pizza) and {"f": "asc"} (ES). The
normalization is idempotent; canonical bodies pass through unchanged.
In-browser tooling (the wasm playground) executes canonical syntax.
Elasticsearch hybrid-search syntax is translated too:
| ES form | Executes as |
|---|---|
top-level knn (one entry or an array) | a vector query — entries (and a sibling query) combine as score-summing bool { should: [...] } clauses, exactly ES’s semantics; knn.filter becomes filter-first kNN (bool { must: [vector], filter }) and knn.similarity becomes the vector query’s min_score |
retriever.standard | the plain query |
retriever.knn | a vector query |
retriever.rrf | retrieve + fuse: { method: "rrf", k: rank_constant }; rank_window_size maps to size |
retriever.linear (per-retriever weight) | retrieve + fuse: { method: "linear", weights } |
match_phrase | the native phrase query (alias) |
?q= query string | the query_string query |
fuzzy with "fuzziness": "AUTO" | natively accepted — the fuzzy query takes 0..=2, "AUTO" or "AUTO:low,high" directly; Pizza defaults to 0 where ES defaults to AUTO |
Parameters that exist in ES but have no Pizza execution yet are rejected,
not approximated: match + fuzziness answers HTTP 400 pointing at the
fuzzy query.
The hybrid (retrieve) pipeline executes retrieve → fuse → pre_filter
→ score_functions → truncate. Stages it does not run — aggs,
collapse, rescore, post_filter — are rejected by name (HTTP 400)
rather than silently skipped. rerank is recognized but unsupported
everywhere (it needs a reranker model serving layer); score_functions
(weight / field_value_factor / decay) executes on BOTH the hybrid and
the plain-query path with identical semantics. Sparse fields join
hybrid retrieval through the vector retriever’s query_sparse
(same id:value forms as the vector query).
Syntax that cannot be translated faithfully — an unknown retriever kind,
retriever combined with query, nested compound retrievers,
query_vector_builder, or a per-retriever knn.filter (use
pre_filter) — is rejected with HTTP 400 rather than approximated.
Point-in-time (PIT) #
The Elasticsearch point-in-time API is supported end-to-end:
# 1. Open — pins every shard's snapshot (keep_alive is required)
POST /my-collection/_pit?keep_alive=2m
→ {"id": "2c2a…", "num_shards": 8, "_shards": {"total": 8, "successful": 8, "failed": 0}}
# 2. Search the pinned view — writes after the open stay invisible
POST /my-collection/_search
{"size": 10, "query": {"match_all": {}}, "pit": {"id": "2c2a…"}}
# 3. Deep pagination over the pinned view with search_after
{"size": 100, "sort": [{"title": "asc"}], "search_after": [...], "pit": {"id": "2c2a…"}}
# 4. Close — frees every pinned snapshot
DELETE /_pit
{"id": "2c2a…"}
→ {"succeeded": true, "num_freed": 8}
Semantics:
- Consistent view — every shard’s snapshot is pinned on the engine copy that opened it; each search executes against those exact snapshots. Documents written after the open (or deleted afterwards) never appear in pit results.
- Routing is carried in the id — the opaque pit id encodes which engine copy on which node holds each shard’s session, so any node can serve a pit search without a cluster-wide registry.
- Keep-alive — every search against a pit renews it for its original
keep_alive. An expired or closed pit answers HTTP 400 (never a silently fresh view), and a pit search that loses ANY pinned shard fails hard rather than returning partial results. - Path targets (
/my-collection/_search) may accompany a pit when they intersect it; disjoint targets answer 400. Shards that joined after the open are invisible to the pit. - Natively, the same capability is the
snapshot_idrequest field — the pit above is its ES-compatible spelling.
Time units #
Whenever durations need to be specified, e.g. for a timeout parameter, the
duration must specify the unit, like 2d for 2 days. The supported units are:
| Suffix | Meaning |
|---|---|
| d | Days |
| h | Hours |
| m | Minutes |
| s | Seconds |
| ms | Milliseconds |
| micros | Microseconds |
| nanos | Nanoseconds |