Endpoint reference
Your node exposes a small set of HTTP endpoints, split across two listeners: a public listener that carries client and relay traffic, and a separate private listener that serves operator-only health and metrics. This page is a map of those endpoints, the role each plays, and the ports they bind by default.
It's a reference, not the byte-level wire specification — the exact request and response schemas, encryption framing, and signing formats are part of the protocol your node software implements, and a full protocol specification is coming soon.
The two listeners
| Listener | Config key | Default | TLS | Carries |
|---|---|---|---|---|
| Public | server.listen | 127.0.0.1:9090 (loopback; bind :9090 to serve) | Serve HTTPS directly (tls.mode: acme/manual), or terminate TLS at a single reverse proxy — never a load balancer | All client and relay traffic: inference, reserve, discovery, attestation, relay |
| Private | server.private_listen | 127.0.0.1:9091 | Never TLS-wrapped | Operator-only /healthz, /livez and /metrics |
The binary's default server.listen is loopback (127.0.0.1:9090) — safe,
but it serves nobody. A serving node must face clients directly: bind all
interfaces with :9090 (dual-stack IPv4 + IPv6 — 0.0.0.0 would be IPv4-only)
and give it HTTPS with tls.mode: acme (or manual), or terminate TLS at a
single reverse proxy. zs-node init writes the public bind for you. The private
listener is loopback-only and never TLS-wrapped — keep it on 127.0.0.1
(or a private network you control) and never expose /healthz, /livez and /metrics to
the internet.
One port instead of two. Set server.private_listen to the empty string
("") to colocate /healthz, /livez and /metrics on the public port — back-compat for
single-port deployments. When you do specify a port in YAML, quote it
(private_listen: ":9091"); a leading colon is otherwise read as a YAML mapping
marker.
Public endpoints
These are served on server.listen (default port 9090).
| Method · Path | Purpose |
|---|---|
POST /v1/responses | Sealed, streamable. The Responses-style inference route — the sealed text path the client drives. Your node decrypts the prompt, runs the model, streams the sealed reply, and returns a signed receipt. |
POST /v1/chat/completions | OpenAI-style chat-completions route, accepted as a relay-forwardable / compatibility path. The current client speaks the Responses shape, not this one, so it isn't part of the client's sealed-and-streamed text path; whether your node serves it natively or translates depends on your provider (see Serving models & pricing). |
POST /v1/images/generations | Sealed. Generates images directly and returns them with a signed receipt. |
POST /v1/images/edits | Sealed (multipart). Edits a supplied image and returns the result with a signed receipt. |
GET /v1/models | Plaintext. Your live model list — what clients read to build the model picker. Always served unencrypted. |
GET /v1/models/{id} | Plaintext. Single-model lookup. |
POST /v1/zs/reserve | Sealed both ways. Two-phase admission, phase 1: returns a signed ticket (price ceiling, expiry) and the wrapped response key. The request uses its own sealed content type (application/vnd.zs-reserve+json) so a relay can't read the payer's address; sealed-only (plaintext requests are rejected 415 bad_content_type). The 200 response is sealed back to the caller too (application/vnd.zs-reserve-response+json), because it carries the pre-signed open transaction, which names the payer's address — a relay would otherwise read it there. Error responses stay plaintext so the caller can see the cause. |
GET /v1/zs/details | Plaintext, unauthenticated. Your operator self-description: live catalog, capacities, protocol version, pricing, oracle/TEE status, and your current signed ephemeral encryption recipient. This is what clients probe for health and discovery and read to build the model picker (see Encryption & keys). (Your NFD name isn't carried here — it resolves from your on-chain operator record.) |
GET /v1/zs/attestation | TEE evidence bundle. Returns the node's attestation evidence when confidential mode is enabled; returns 404 when TEE is disabled. See Confidential compute (TEE). |
POST /v1/zs/relay | The transport hop other operators' requests arrive through. Your node forwards a sealed payload to a named target node and streams the response back, without being able to read it. Registered only when a relay directory is available (escrow + algod configured). See Relays. |
/v1/responses is the sealed text path. The current client seals and
streams text over /v1/responses only. /v1/chat/completions is accepted for
relay forwarding and OpenAI-shape compatibility, but it isn't the route the
client drives. Which shape your upstream serves natively (versus the node
translating between them) depends on your provider; see
Serving models & pricing.
Private endpoints
These are served on server.private_listen (default port 9091), loopback-only
and never TLS-wrapped. Point your supervisor and Prometheus scraper at this
listener, not the public one. All three routes are exempt from rate limiting.
| Method · Path | Purpose |
|---|---|
GET /healthz | Readiness probe — should traffic go here? 200 when serving, 503 {"status":"draining"} for the whole graceful shutdown, which is what pulls the node out of your load balancer. |
GET /livez | Liveness probe — is the process wedged? 200 whenever the node is responsive, including while draining. Point container liveness checks here, never at /healthz. On a node build that predates it this returns 404 — run no liveness probe at all on those, rather than falling back to /healthz. |
GET /metrics | Prometheus exposition — provider health, discovered model count, settlement state, and the rest of the node's operational metrics. |
How the public routes relate
A normal request touches the public routes in this order:
Discover — /v1/zs/details
The client probes it to discover you (continuously, in the background).
Reserve — /v1/zs/reserve
The client calls it to get a signed price ceiling and the keys for the request.
Run the model
The client calls the text route (/v1/responses) or an image route
(/v1/images/generations or /v1/images/edits) to run the model.
Receipt & settlement
Your node returns a signed receipt; settlement follows on-chain (see The payment flow).
When your node is acting as a relay for someone else's request, that request
arrives at /v1/zs/relay instead, and you forward it to the target's
reserve / inference / image endpoints on their behalf — sealed, so you never see
its contents.
Sealed envelopes
The prompt-carrying routes don't accept plaintext. A sealed request carries the
content type application/vnd.zs+json; a POST to one of these routes that
isn't a proper envelope is rejected with 400 bad_envelope. Sealed responses
come back the same way — a application/vnd.zs+json body with the response key
delivered out-of-band — and stream as sealed event: zs frames when the
client requests streaming. The plaintext discovery routes (/v1/models,
/v1/models/{id}, /v1/zs/details) and the private routes are the
exceptions: they're served unencrypted by design.
The exact envelope framing, headers, and signing formats are the protocol's business, not something you configure — your node software implements them. See Encryption & keys for how the sealing keys are generated, signed, and rotated.
Transport requirements
- HTTPS at a publicly reachable URL for the public listener. Clients and
relays both need to reach your advertised base URL from the open internet, with
TLS terminated either by your reverse proxy or by the node (
tls.mode: acme/manual). - Loopback-only for the private listener. Keep
/healthz,/livezand/metricson127.0.0.1(or a trusted private interface). It is never TLS-wrapped — don't expose it. - Streaming. The text routes stream; relays must forward streamed responses without buffering the whole reply.
- Current protocol version. The version you advertise in
/v1/zs/detailsmust match what clients speak, or you're flagged incompatible and skipped (see Health & compatibility).
Coming soon. A full protocol specification — exact paths, headers, payload schemas, encryption framing, and signing formats — will document the byte-level contract behind these tables. Until then, the node software is the reference implementation of these endpoints.