API conventions

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 formExecutes 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.standardthe plain query
retriever.knna vector query
retriever.rrfretrieve + fuse: { method: "rrf", k: rank_constant }; rank_window_size maps to size
retriever.linear (per-retriever weight)retrieve + fuse: { method: "linear", weights }
match_phrasethe native phrase query (alias)
?q= query stringthe 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_id request 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:

SuffixMeaning
dDays
hHours
mMinutes
sSeconds
msMilliseconds
microsMicroseconds
nanosNanoseconds
Calendar October 6, 2026
Edit Edit this page