Cap what a single request may cost
Providers compete on price, so the cost of a model is a range rather than a number, and the provider that serves you can change between requests. The X-0G-Provider-Max-Price-Usd-* headers turn that range into a bound you control: providers above your ceiling are removed from the candidate pool before routing happens at all. This page shows how to set the ceiling on chat and image requests, and what to do when the ceiling leaves nothing to route to.
Prerequisites
Section titled “Prerequisites”- An account with a funded balance. See Get 0G and fund your account.
- An
sk-API key from the 0G Private Computer Console, exported asZG_API_KEY. - A sense of what your requests currently cost. Every response carries
x_0g_trace.billing; read a few before you pick a number.
The chat examples use glm-5.2; the image example uses z-image-turbo.
The three headers
Section titled “The three headers”| Header | Unit | Applies to |
|---|---|---|
X-0G-Provider-Max-Price-Usd-Prompt |
USD per 1M tokens | Chat endpoints only |
X-0G-Provider-Max-Price-Usd-Completion |
USD per 1M tokens | Chat endpoints only |
X-0G-Provider-Max-Price-Usd-Image |
USD per image | Image endpoints only |
Each value is a finite, non-negative decimal. Full value domain and rejection rules: Provider routing headers.
A header sent to the wrong kind of endpoint is silently inert — an image ceiling on a chat request neither filters nor errors. That silence is worth remembering, because a typo in the header name behaves identically. Speech-to-text is billed per second and does not support a price ceiling yet.
Cap a chat request
Section titled “Cap a chat request”Set one or both token ceilings. They are independent filters: a provider must satisfy every ceiling you send.
curl https://router-api.0g.ai/v1/chat/completions \ --max-time 120 \ --fail-with-body \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZG_API_KEY" \ -H "X-0G-Provider-Max-Price-Usd-Prompt: <YOUR_PROMPT_CEILING>" \ -H "X-0G-Provider-Max-Price-Usd-Completion: <YOUR_COMPLETION_CEILING>" \ -d '{ "model": "glm-5.2", "messages": [{"role": "user", "content": "Summarize this in one sentence."}] }'import OpenAI from "openai";
const apiKey = process.env.ZG_API_KEY;if (!apiKey) throw new Error("ZG_API_KEY is not set");
const client = new OpenAI({ apiKey, baseURL: "https://router-api.0g.ai/v1", timeout: 120_000, maxRetries: 0, // an empty pool is structural; retrying it is wasted work});
type ZgTrace = { request_id: string; provider: string; billing: { input_cost: string; output_cost: string; total_cost: string };};
async function askCapped(prompt: string, promptCeiling: string, completionCeiling: string) { try { const res = await client.chat.completions.create( { model: "glm-5.2", messages: [{ role: "user", content: prompt }], }, { headers: { "X-0G-Provider-Max-Price-Usd-Prompt": promptCeiling, "X-0G-Provider-Max-Price-Usd-Completion": completionCeiling, }, }, );
const trace = (res as unknown as { x_0g_trace: ZgTrace }).x_0g_trace; console.log(trace.request_id, trace.provider, trace.billing.total_cost); return res.choices[0].message.content ?? ""; } catch (err) { if (err instanceof OpenAI.APIError) { const code = (err.error as { code?: string } | undefined)?.code; if (code === "no_provider_within_max_price") { // The pool is empty by your own filter. Raise the ceiling or change model. throw new Error("no provider under the configured price ceiling"); } throw new Error(`router error ${err.status} (${code})`); } throw err; }}import os
from openai import APIError, APIStatusError, OpenAI
api_key = os.environ["ZG_API_KEY"]
client = OpenAI( api_key=api_key, base_url="https://router-api.0g.ai/v1", timeout=120.0, max_retries=0, # an empty pool is structural; retrying it is wasted work)
def ask_capped(prompt: str, prompt_ceiling: str, completion_ceiling: str) -> str: try: res = client.chat.completions.create( model="glm-5.2", messages=[{"role": "user", "content": prompt}], extra_headers={ "X-0G-Provider-Max-Price-Usd-Prompt": prompt_ceiling, "X-0G-Provider-Max-Price-Usd-Completion": completion_ceiling, }, ) except APIStatusError as err: code = (err.response.json().get("error") or {}).get("code") if code == "no_provider_within_max_price": # The pool is empty by your own filter. Raise the ceiling or change model. raise RuntimeError("no provider under the configured price ceiling") from err raise except APIError as err: raise RuntimeError(f"router error: {err}") from err
trace = (res.model_extra or {}).get("x_0g_trace", {}) print(trace.get("request_id"), trace.get("provider"), trace.get("billing", {}).get("total_cost")) return res.choices[0].message.content or ""Cap an image request
Section titled “Cap an image request”Image generation is priced per image, so it uses its own header and its own unit.
curl https://router-api.0g.ai/v1/images/generations \ --max-time 300 \ --fail-with-body \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZG_API_KEY" \ -H "X-0G-Provider-Max-Price-Usd-Image: <YOUR_IMAGE_CEILING>" \ -d '{ "model": "z-image-turbo", "prompt": "A lighthouse at dusk" }'The token ceilings do not apply here, and the image ceiling does not apply to chat. Sending the wrong one is inert rather than an error.
The filter runs before routing
Section titled “The filter runs before routing”This is the property that makes the ceiling trustworthy: it is a hard filter applied before sorting and before failover. A provider over your ceiling is not deprioritized, it is not eligible. So during an outage, when the providers under your ceiling are exactly the ones that are down, the request fails instead of falling back to an expensive provider you had already ruled out.
The trade is explicit. A tight ceiling means a smaller pool, and a smaller pool has less to fail over to. See Handle failover and retries for the availability side of that bargain.
Expected output
Section titled “Expected output”The response is an ordinary completion. Confirm the ceiling did what you wanted by reading the x_0g_trace block: provider tells you who served it, billing tells you what it cost.
{ "id": "chatcmpl-0852f405-6c56-40c2-a800-e6fd70785065", "object": "chat.completion", "model": "glm-5.2", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" } ], "x_0g_trace": { "request_id": "b41d7c92-5e83-4a6f-9c17-3d8e2f5a6b09", "provider": "0xB01EBd79c3fd63ff52fD47C3935119601EEe2FdB", "billing": { "input_cost": "32890000000000", "output_cost": "1914160000000000", "total_cost": "1947050000000000" } }}Live response, 2026-07-20.
When the ceiling excludes every candidate, you get a 400 instead:
{ "error": { "message": "failed to select provider: no provider available within max_price_usd: prompt<=1e-06 completion<=0 (USD per 1M tokens) image<=0 (USD per image)", "type": "invalid_request_error", "code": "no_provider_within_max_price" }, "request_id": "ddb21d95-f541-441b-b507-de30f5349e9a"}Live response, 2026-07-20.
Common errors
Section titled “Common errors”| Status | code |
Cause | Fix |
|---|---|---|---|
| 400 | no_provider_within_max_price |
Every candidate is above your ceiling | Raise the ceiling or pick another model. Do not retry: the pool is empty for structural reasons, not transient ones |
| 400 | pinned_provider_exceeds_max_price |
X-0G-Provider-Address names a provider above the ceiling |
The pin and the ceiling contradict each other. Drop one |
| 400 | invalid_max_price_usd |
Value is not a finite non-negative decimal | Send a plain decimal with no currency symbol or unit suffix |
| — | — | The header appears to do nothing | It was sent to the wrong endpoint kind, or the name is misspelled. Cross-dimension headers are inert, not rejected |
Full list with retry guidance: Error codes.
Next steps
Section titled “Next steps”- Handle failover and retries — what a smaller candidate pool costs you in availability
- Provider routing headers — every
X-0G-Provider-*header, its default, and its rejection rule