Tenzro
AI

OpenAI-compatible API.

OpenAI wire-format HTTP surface for chat completions, model listing, and embeddings. Existing OpenAI SDK clients work unchanged by pointing base URL at a Tenzro node.
STATUS
Testnet
CRATE
tenzro-node
STABILITY
Stable
TYPE
HTTP Surface
01

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 shape

Listing 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).

02

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,
});
03

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.

04

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.

05

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.

06

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.

07

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.

08

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 back

draft_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.

09

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.

10

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
Related
← All docs