Provider Routing Headers
The X-0G-Provider-* request headers control routing: which provider serves a request, what trust tier it must meet, and what you are willing to pay. This page gives the value domain, the default, and the exact error each malformed value produces.
Header names are case-insensitive per RFC 7230. X-0G-Provider-Sort and x-0g-provider-sort are the same header. Header values follow the per-header rules below.
Authorization
Section titled “Authorization”Every authenticated endpoint uses the same header. The prefix decides what the credential can do.
Authorization: Bearer sk-<YOUR_API_KEY>Authorization: Bearer mk-<YOUR_MANAGEMENT_KEY>| Prefix | What it is for |
|---|---|
sk- |
Inference endpoints. Billed against your deposit. |
mk- |
Account administration: balance, usage, and sk- key management. Not billed. |
There is no OAuth flow and no per-request wallet signature. Missing header gives 401 missing_authorization; an unknown or malformed key gives 401 invalid_api_key. See API & Authentication for the endpoint-by-credential matrix, and Error Codes for every code.
Routing surfaces and priority
Section titled “Routing surfaces and priority”Routing preferences arrive from two surfaces:
| Priority | Surface | Endpoints | Status |
|---|---|---|---|
| 1 | X-0G-Provider-* request headers |
All inference endpoints, including JSON, multipart, and async | Canonical |
| 2 | JSON body provider object |
JSON endpoints only | Deprecated |
The two are merged field by field. When both set the same field, the header wins; a field set only in the body still applies. Multipart endpoints have no body routing surface at all, so headers are the only way to route there.
Absent, blank, and malformed
Section titled “Absent, blank, and malformed”The Router draws a hard line between “I did not set this” and “I set this wrong”:
- Absent, or blank after trimming whitespace — treated as unset. The default applies. Never an error.
- Present but malformed — rejected with a
400. The Router does not silently fall back to a default, because a header that quietly does nothing trains you to believe a setting took effect when it did not.
A client that unconditionally emits X-0G-Provider-Sort: with an empty value is safe. A client that emits X-0G-Provider-Sort: fastest gets a 400.
Request headers
Section titled “Request headers”| Header | Values | Default | Invalid value |
|---|---|---|---|
X-0G-Provider-Address |
On-chain address (0x…) |
unset — Router selects | Rejected by the provider registry |
X-0G-Provider-Sort |
latency or price, exact lowercase |
unset — Router default strategy | 400 invalid_provider_header |
X-0G-Provider-Trust-Mode |
standard, verified, or private |
unset — no tier restriction | 400 invalid_trust_mode |
X-0G-Provider-Allow-Fallbacks |
true or false, case-insensitive |
true; false when an address is pinned |
400 invalid_provider_header |
X-0G-Provider-Require-Parameters |
true or false, case-insensitive |
false — soft preference |
400 invalid_provider_header |
X-0G-Provider-Max-Price-Usd-Prompt |
Finite, non-negative decimal | unset — no ceiling | 400 invalid_max_price_usd |
X-0G-Provider-Max-Price-Usd-Completion |
Finite, non-negative decimal | unset — no ceiling | 400 invalid_max_price_usd |
X-0G-Provider-Max-Price-Usd-Image |
Finite, non-negative decimal | unset — no ceiling | 400 invalid_max_price_usd |
Case rules
Section titled “Case rules”| Element | Case sensitivity |
|---|---|
| Header names | Insensitive, all headers |
X-0G-Provider-Sort value |
Sensitive — must be exactly latency or price |
Allow-Fallbacks and Require-Parameters values |
Insensitive — TRUE and true both work |
X-0G-Provider-Trust-Mode value |
Use the exact lowercase tier names |
X-0G-Provider-Address
Section titled “X-0G-Provider-Address”Pins the request to one provider by on-chain address. When set, X-0G-Provider-Sort is ignored, since there is nothing to sort. Pinning also implies Allow-Fallbacks: false: if the pinned provider fails, so does the request. Send X-0G-Provider-Allow-Fallbacks: true alongside it to re-enable cross-provider retry.
Find addresses with GET /v1/providers?model=… — see Model Catalog.
X-0G-Provider-Sort
Section titled “X-0G-Provider-Sort”latency routes to the provider with the lowest recently observed latency for the model. price routes to the cheapest. The value must be exactly latency or price; Latency and LATENCY are rejected, unlike the boolean headers.
X-0G-Provider-Trust-Mode
Section titled “X-0G-Provider-Trust-Mode”Restricts selection to a trust tier. The tiers are ordered standard < verified < private and act as a floor: asking for verified is also satisfied by the stronger private.
| Value | Routes to | Guarantee |
|---|---|---|
standard |
Any TEE-backed provider | TEE-backed execution; the upstream discloses no independent verifiability method |
verified |
TeeML and TeeTLS providers | Verifiable execution — the response provably came from the real model |
private |
TeeML providers only | Verifiability and privacy — the model runs inside the enclave, so prompts never leave it |
Omit the header for no restriction, which lets the Router balance across tiers for performance.
The Router never silently downgrades. If no provider in the requested tier is available, the request fails with 503 no_provider_for_trust_mode rather than serving you from a weaker tier. That condition is about tier supply and is transient: retry, or pick a model with providers in that tier. An out-of-domain value gives 400 invalid_trust_mode.
A trust mode set per key overrides anything your code sends. See Trust Modes for the concept.
curl https://router-api.0g.ai/v1/chat/completions \ --max-time 120 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZG_API_KEY" \ -H "X-0G-Provider-Trust-Mode: private" \ -H "X-0G-Provider-Sort: latency" \ -d '{ "model": "<MODEL_ID>", "messages": [{"role": "user", "content": "Hello"}] }'X-0G-Provider-Allow-Fallbacks
Section titled “X-0G-Provider-Allow-Fallbacks”Controls cross-provider retry when a provider fails. Only true and false are accepted, case-insensitively. 1, 0, and yes — the common SDK mistakes — return 400 invalid_provider_header.
X-0G-Provider-Require-Parameters
Section titled “X-0G-Provider-Require-Parameters”By default the Router treats requested sampling and reasoning parameters as a soft preference: among providers that pass every hard filter, it prefers ones advertising support for what you asked for, and falls back to the rest if none do. You never get a 400 for this, and no usable provider is discarded.
Setting X-0G-Provider-Require-Parameters: true upgrades that preference to a hard filter for this request. Providers that do not advertise support are dropped, and if none remain the request fails with 400 model_not_capable.
It also brings output capacity into the filter. When the header is on and the request sets a maximum output length, the Router routes only to providers whose advertised max_completion_tokens can hold it, including excluding providers that have never reported a cap. No qualifying provider gives 400 no_provider_within_max_tokens. With the header off, an unreported cap is treated as unknown rather than incapable, and the provider is left to reject the request itself.
A pinned X-0G-Provider-Address skips these filters.
X-0G-Provider-Max-Price-Usd-*
Section titled “X-0G-Provider-Max-Price-Usd-*”A hard ceiling on what you will pay, in USD. Any provider above the ceiling on a relevant dimension is dropped from the candidate pool entirely.
The filter runs before sorting and before failover. That ordering is the point: a fallback during an outage can never route you to a provider you have priced out. It is a filter, not a preference.
Send any subset — one header, two, or all three. Each value is a finite, non-negative decimal; NaN, Inf, negatives, and non-numeric values give 400 invalid_max_price_usd.
The ceiling is service-type aware:
| Service type | Endpoints | Dimensions enforced | Unit |
|---|---|---|---|
| Chat | /v1/chat/completions, /v1/messages |
Prompt, Completion |
USD per 1M tokens |
| Image | /v1/images/generations, /v1/images/edits, /v1/async/images/* |
Image |
USD per generated image |
| Speech-to-text | /v1/audio/transcriptions |
none yet | — |
Sending Image to a chat call, or Prompt to an image call, is silently inert. A cross-endpoint SDK that always sends all three headers will not accidentally filter every provider out. Speech-to-text is billed per second of audio, which has no equivalent dimension in the current pricing schema, so no ceiling is enforced there yet.
Two failure modes worth designing for:
- No provider qualifies —
400 no_provider_within_max_price, not503. The pool is empty structurally, not transiently, so retrying without raising the ceiling will not help. - Pin above the ceiling —
400 pinned_provider_exceeds_max_price. The pin is never silently overridden.
Response headers
Section titled “Response headers”| Header | Present on | Meaning |
|---|---|---|
X-RateLimit-Limit-Requests |
Every inference response | Your current request limit |
X-RateLimit-Remaining-Requests |
Every inference response | Requests left in the current window |
X-RateLimit-Reset-Requests |
Every inference response | ISO-8601 timestamp when the window resets |
Retry-After |
429 responses |
Seconds to wait before retrying |
ZG-Res-Key |
Every inference response | The provider’s chat ID for this response |
Observing and reacting to limits
Section titled “Observing and reacting to limits”Read X-RateLimit-Remaining-Requests on every response. A batch job that watches it approach zero and slows down never trips a 429 at all, which is strictly better than absorbing rejections that still cost a round trip. Limits are per account, not per key: issuing more sk- keys does not buy more throughput.
Threshold numbers are not published, because they depend on account state and change as the network is tuned. A hardcoded number would be wrong for your account or wrong next month. X-RateLimit-Limit-Requests reports your real limit on every response.
When you do exceed the limit, the Router returns 429 with Retry-After in seconds. Honor it; a tight retry loop only collects more 429s. See the 429 row in Error Codes.
curl -s -D headers.txt -o /dev/null \ https://router-api.0g.ai/v1/chat/completions \ --max-time 120 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZG_API_KEY" \ -d '{"model": "<MODEL_ID>", "messages": [{"role": "user", "content": "Hello"}]}'
grep -i 'retry-after\|x-ratelimit\|zg-res-key' headers.txtZG-Res-Key
Section titled “ZG-Res-Key”ZG-Res-Key carries the chatID you need to verify a response independently of the Router, by fetching the provider’s signature and checking it yourself rather than trusting the Router’s tee_verified flag. Prefer it over the response body id, which is only a fallback. See Trust Modes.
Errors these headers can produce
Section titled “Errors these headers can produce”| Code | Status | Trigger |
|---|---|---|
invalid_provider_header |
400 | Out-of-domain Sort, Allow-Fallbacks, or Require-Parameters value |
invalid_trust_mode |
400 | Trust-Mode outside standard / verified / private |
invalid_max_price_usd |
400 | A price value that is not a finite, non-negative decimal |
no_provider_within_max_price |
400 | The price ceiling filtered out every candidate |
pinned_provider_exceeds_max_price |
400 | The pinned address is priced above the ceiling |
model_not_capable |
400 | Require-Parameters: true and no provider supports the requested parameters |
no_provider_within_max_tokens |
400 | Require-Parameters: true and no provider advertises enough output capacity |
no_provider_for_trust_mode |
503 | No provider in the requested trust tier is available |
rate_limit_exceeded |
429 | Over the request limit; read Retry-After |
Full table with retry guidance: Error Codes.
Next steps
Section titled “Next steps”- Error Codes — every status, type, code, and what is retryable
- Model Catalog — finding a model ID or a pinnable provider address
- Trust Modes — what
standard,verified, andprivateactually guarantee