Skip to content

Track what each request costs

Every response tells you what it cost. There is no separate metering call and no waiting for a statement to close: the Router attaches the exact charge for a request to that request’s own reply, alongside the identifier you need to look it up later. This page shows how to read that block and get it into your logs in a shape you can query.

The examples use glm-5, matching the live responses shown further down.

Cost for a chat request is:

input_tokens × prompt_price + output_tokens × completion_price

Prices are declared by the provider that served the request, so the same model has a range rather than a single number, and the provider can differ between calls. Current per-model prices are listed in the Console; this page deliberately does not repeat them.

You do not have to compute any of that yourself. Every response carries an x_0g_trace block with the charge already resolved:

Field Meaning
request_id Identifier for this call. The join key between your logs and support
provider On-chain address of the provider that served it
billing.input_cost Charge attributed to input tokens — the whole context you sent, not just the last message
billing.output_cost Charge attributed to generated tokens
billing.total_cost What this request debited from your balance

Every cost is a string holding an integer in the smallest unit of 0G — 1018 of them make one 0G, the same convention as wei on other EVM chains. A response reporting "total_cost": "12458460000000000" charged 0.01245846 0G.

Two consequences for your code:

  • Do not parse them as floats. These values exceed Number.MAX_SAFE_INTEGER in JavaScript and lose precision silently. Use BigInt in TypeScript and int in Python, converting to a decimal only for display.
  • Do not divide too early. Sum the integers, then divide once by 1018 at the end. Dividing per request and adding floats accumulates rounding error across a day of traffic.
const NEURON_PER_0G = 10n ** 18n;
const total = BigInt(trace.billing.total_cost); // exact
const display = Number(total) / Number(NEURON_PER_0G); // display only

Logging provider next to total_cost is what makes a price ceiling possible later: you cannot pick a sensible ceiling without knowing which providers your spend actually goes to.

Write these as structured fields, not as a printed line. Cost per request only answers questions if you can group it by model, provider, and key.

Terminal window
curl https://router-api.0g.ai/v1/chat/completions \
--max-time 120 \
--fail-with-body \
--silent \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ZG_API_KEY" \
-d '{
"model": "glm-5",
"messages": [{"role": "user", "content": "Summarize this in one sentence."}]
}' \
| jq -c '{
event: "inference",
request_id: .x_0g_trace.request_id,
provider: .x_0g_trace.provider,
model: .model,
input_cost: .x_0g_trace.billing.input_cost,
output_cost: .x_0g_trace.billing.output_cost,
total_cost: .x_0g_trace.billing.total_cost
}'

The x_0g_trace block sits alongside the standard OpenAI-compatible fields:

{
"id": "chatcmpl-1c487c99-ff82-4b4d-9898-0bd11ba5acd9",
"object": "chat.completion",
"created": 1784535661,
"model": "glm-5",
"system_fingerprint": null,
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "Hello there, how are you?" },
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 11,
"prompt_tokens_details": { "cached_tokens": 0 },
"completion_tokens": 142,
"completion_tokens_details": { "reasoning_tokens": 133 },
"total_tokens": 153
},
"x_0g_trace": {
"request_id": "65cc8e6e-e4d6-49b9-b6b1-7e3d2596436c",
"provider": "0xB01EBd79c3fd63ff52fD47C3935119601EEe2FdB",
"billing": {
"input_cost": "32890000000000",
"output_cost": "1914160000000000",
"total_cost": "1947050000000000"
}
}
}

Live response, 2026-07-20. Note completion_tokens_details.reasoning_tokens: reasoning models bill for thinking tokens you never see in content, which is usually the reason a short answer costs more than expected.

A client that rejects unknown JSON keys will choke on x_0g_trace. Tolerate unrecognized fields.

request_id is cheap to store and expensive to not have. The Router keeps billing metadata but never keeps your prompts or completions, so once a request is done, the ID is the only thing that ties a charge back to the call your system made.

Two things it buys you:

  • Reconciliation. Your own per-request log, summed over a window, should match what the account usage endpoint reports for that window. Divergence is only diagnosable if both sides share an identifier. See Monitor balance and usage.
  • Verification. Checking that a response really came from the enclave that claimed it is a per-response operation, and it starts from the identifiers on that response. See Verify a response came from the enclave.

Log it on failures as well as successes. Error bodies carry request_id at the top level, and a failed request is exactly the case you will be asked about.

Streaming does give you the cost — in the last chunk. Intermediate chunks carry "usage": null and no trace, but the final chunk before data: [DONE] has an empty choices array, the complete usage, and the full x_0g_trace:

{
"id": "chatcmpl-2a23c437-775d-4f28-b25c-824e8ba14e0b",
"object": "chat.completion.chunk",
"model": "glm-5",
"choices": [],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 922,
"completion_tokens_details": { "reasoning_tokens": 919 },
"total_tokens": 932
},
"x_0g_trace": {
"request_id": "8312e180-3ecb-43d3-8fd9-06fe5777a59a",
"provider": "0xB01EBd79c3fd63ff52fD47C3935119601EEe2FdB",
"billing": {
"input_cost": "29900000000000",
"output_cost": "12428560000000000",
"total_cost": "12458460000000000"
}
}
}

Live response, 2026-07-20. Two things follow: a loop that stops at the first chunk with finish_reason: "stop" will miss the cost entirely, so read to [DONE]; and a client that assumes every chunk has choices[0] will throw on this one. See Stream responses.

Status code Cause Fix
401 invalid_api_key Key is wrong, or an mk- key was used for inference Send an sk- key on /v1/chat/completions
402 insufficient_balance Balance exhausted, so the request was never served and has no billing block Fund the account; do not retry
429 rate_limit_exceeded Too many requests Honour Retry-After
x_0g_trace is missing from a response The call was streamed, or a client library dropped unknown fields

Full list with retry guidance: Error codes.