OpenAI-compatible API.
- STATUS
- Testnet
- CRATE
- tenzro-node
- STABILITY
- Stable
- TYPE
- HTTP Surface
Routes
The node serves an OpenAI-compatible HTTP surface for clients and aggregators that speak the OpenAI wire format.
GET /v1/models every model this gateway can serve
GET /v1/models/{id} one model by instance ID or model ID
POST /v1/chat/completions chat completions, streaming and non-streaming
POST /api/paid/chat/completions same handler behind the HTTP 402 payment gate
POST /v1/embeddings text embeddings in the OpenAI wire shapeListing metadata derives from registry and gossip-announcement state — providers do not maintain a separate listing configuration. /v1/embeddings is served by any loaded ONNX encoder, local or a network provider; input is a string or an array, and dimensions requests Matryoshka truncation on models that support it (EmbeddingGemma, Qwen3-Embedding).
Client setup
Point any OpenAI SDK at the Tenzro node's base URL. No client changes.
# Python OpenAI SDK
from openai import OpenAI
client = OpenAI(
base_url="https://rpc.tenzro.xyz/v1",
api_key="tnz_...", # or any non-empty string for the ungated route
)
resp = client.chat.completions.create(
model="qwen3-8b",
messages=[{"role": "user", "content": "hello"}],
stream=True,
)// TypeScript OpenAI SDK
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://rpc.tenzro.xyz/v1",
apiKey: "tnz_...",
});
const resp = await client.chat.completions.create({
model: "qwen3-8b",
messages: [{ role: "user", content: "hello" }],
stream: true,
});Model listing entry
Each entry in GET /v1/models carries the OpenAI core fields plus the Tenzro serving contract:
{
"id": "svc-8f3a…",
"object": "model",
"created": 1780560000,
"owned_by": "0x4b2c…",
"model_id": "qwen3-8b",
"model_name": "Qwen 3 8B",
"location": "local",
"status": "online",
"context_length": 32768,
"max_output_tokens": 2048,
"pricing": {
"input_wei_per_token": "1000000000000",
"output_wei_per_token": "3000000000000",
"minimum_wei": "0",
"pricing_model": "PerToken"
},
"features": {
"streaming": true,
"usage_in_stream": true,
"mtp": true,
"provenance_signing": true,
"jurisdiction_signing": true,
"supported_parameters": ["temperature", "top_p", "max_tokens", "stream", "draft_n", "verifiable", "jurisdiction", "jurisdiction_receipt"]
},
"datacenter_location": "us-central1",
"api_endpoint": "https://…",
"mcp_endpoint": "https://…"
}id — for local service instances, the instance ID; for gossip-discovered network models, the model ID. Either form is accepted as model in a chat request. context_length / max_output_tokens come from the model registry, falling back to the built-in catalog; null when neither source knows. pricing is wei per token as decimal strings (values exceed JSON's safe integer range). datacenter_location is the provider's declared geography from its gossip announcement — null means undeclared, not global.
Data policy
The list response carries a top-level data_policy object — the gateway's machine-readable data-handling declaration:
{
"object": "list",
"data": [ … ],
"data_policy": {
"prompt_retention": "none",
"completion_retention": "none",
"stream_resume_buffer_secs": 300,
"trains_on_data": false,
"usage_accounting": "token_counts_cost_latency_only"
}
}Prompt and completion bodies are never written to disk. SSE chunks live in an in-memory resume buffer for stream_resume_buffer_secs, then expire. The usage tracker records token counts, cost, and latency only.
Streaming usage chunks
Streaming responses always include usage — no stream_options.include_usage opt-in is required. The stream ends with three frames:
{"id": "chatcmpl-…", "object": "chat.completion.chunk", "created": 1780560000, "model": "qwen3-8b",
"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
"usage": {"prompt_tokens": 12, "completion_tokens": 48, "total_tokens": 60}}
{"id": "chatcmpl-…", "object": "chat.completion.chunk", "created": 1780560000, "model": "qwen3-8b",
"choices": [],
"usage": {"prompt_tokens": 12, "completion_tokens": 48, "total_tokens": 60},
"cost_wei": "156000000000000", "generation_time_ms": 2140, "tokens_per_second": 22.4}
data: [DONE]cost_wei, generation_time_ms, and tokens_per_second are extension fields; OpenAI SDK parsers ignore them. Non-streaming responses carry the same usage object plus the same extension fields at the top level. When the request targets a network model, the node proxies the upstream SSE byte stream unchanged — usage chunks emitted by the serving node pass through to the client.
Stream resume
Streams support reconnection via the SSE Last-Event-ID header. Every event's id is <completion_id>:<seq>; a reconnecting client sends Last-Event-ID: <completion_id>:<seq> and the node replays buffered chunks with a higher sequence number, synthesizing [DONE] if the stream already finished. The buffer is in-memory and expires per data_policy.stream_resume_buffer_secs.
Streaming failover
The Last-Event-ID resume above covers a dropped client connection. When the drop is on the provider leg — the gateway is proxying a network model and the serving provider dies mid-generation — the gateway continues the stream on a different provider without a visible restart. It captures the sampling state as it forwards tokens (messages, seed, temperature, top_p, max_tokens, and the assistant text emitted so far), then re-sends to another provider with the emitted text as a trailing assistant prefix and continue_final_message: true. The seed travels with the request so the new provider re-prefills the identical prefix and resumes sampling from the same distribution. No KV-cache bytes cross the wire. Bounded single retry: a second drop ends the stream. Pin seed for byte-identical continuation.
Tenzro extensions
The wire shape stays OpenAI-compatible; Tenzro-specific parameters ride on the request body as extension fields. OpenAI SDKs pass unknown fields through unchanged.
draft_n 1..6, opt in to Multi-Token-Prediction speculative decoding
verifiable true, request a TOPLOC top-k logit commitment
jurisdiction "DE,EU", hard-filter to providers matching the locality pin
jurisdiction_receipt "required", fail unless a signed locality receipt comes backdraft_n — the target model must be catalog-marked as MTP-capable (see features.mtp in the model listing). verifiable — non-streaming, local single-token path only; externally-fronted engines (vLLM, SGLang) return no commitment. jurisdiction — refusals return HTTP 412 with error codes jurisdiction_not_satisfied or jurisdiction_receipt_unavailable. See Inference for the routing behavior behind each flag.
Payment gate
/v1/chat/completions is open; /api/paid/chat/completions is the same handler behind an HTTP 402 payment gate. When the gate is enabled, unauthenticated requests to the paid path get a 402 Payment Required challenge carrying the payment protocol descriptor (MPP, x402, or native TNZO channel). Present a valid credential on retry and the request proceeds. The ungated route accepts any non-empty bearer token — API-key gating on the ungated route is per-operator via X-Tenzro-Api-Key.
Errors
400 malformed request body
401 missing or invalid API key on a key-gated route
402 payment required on the paid route
404 unknown model id
412 jurisdiction_not_satisfied | jurisdiction_receipt_unavailable
429 rate limited
502 upstream provider failed after failover budget exhausted
504 per-request deadline exceeded before any provider succeeded