How the price of a text request is calculated, start to finish
Between sending a request and paying for it there are several steps, and nearly every one is a place to either save money or lose track of spend.
5 min · · Updated · payments
Call /v1/messages or /v1/chat/completions, and before a single token shows up in the response, the gateway runs through several internal steps. Walking through them explains why a request sometimes fails with 402 instantly, and why X-Zukko-Cost-Micros in the response doesn't always match a rough estimate.
Step 1: counting input tokens
Everything that goes into the messages field — the system prompt, conversation history, file contents pasted in, tool descriptions for an agent — gets tokenized for the specific model you picked. Each model has its own tokenizer, so the same piece of text produces a different token count for claude-opus-5-5 than for gpt-5.6-sol. If you need the token count without sending a real request or paying for it, there's a dedicated free method:
curl https://api.zukko.pro/v1/messages/count_tokens \
-H "Authorization: Bearer fetch_your_key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-opus-5-5","messages":[{"role":"user","content":"hello"}]}'This is handy when context is assembled programmatically from file fragments and history, and you want to estimate size before sending rather than after paying for it.
Step 2: the balance check happens before the model call
Next the gateway checks the account balance and any limits set on the specific key, if you configured them in the cabinet on the key's page. This happens before the request ever reaches the model provider. If there's no money left, the response comes back instantly:
{"error":{"message":"Insufficient balance","code":"insufficient_balance"}}The important detail: since the check runs before the model call, a failed request with a 402 costs nothing. The same step checks key limits — the daily and monthly dollar cap and the list of allowed models. If a particular model is blocked by the key's policy, you get a 403 model_not_allowed_for_key instead of a charge followed by a refusal.
Step 3: the request goes to the model — but the cache is checked first
If balance and limits are fine, the gateway checks the cache. With an explicit X-Zukko-Cache: true header (the primary way, works for any model) or temperature: 0 (only for models that accept that value; newer Claude models may reject a temperature other than 1 with a 400), the same request sent earlier may already be stored — for chat, the cache lives for up to 7 days. The match has to be exact: the whole request body, not a "nearly identical" request or a shared prefix. On a hit, the response comes back instantly and free, with X-Zukko-Cache: hit in the headers; such hits are not recorded in /v1/usage. If there's no match, the request body is converted into the target provider's format and actually sent.
Step 4: output tokens and the final price
Once the model responds, the gateway counts output tokens and adds up the cost: input and output tokens are multiplied by the model's rates. For the models currently enabled in Zukko, input and output cost the same — claude-sonnet-5 and gpt-5.6-sol at $0.30 per million, glm-5.3-flash at $0.04, for example (prices are at GET /v1/models/info). Images work differently — pricing there is a fixed amount per generated image, not per token, and it's billed separately for nano-banana-2-* and openai-gpt-image-* (image generation through Zukko is temporarily unavailable right now). The total is deducted from the account balance right after the model's response arrives, with no separate confirmation step.
Step 5: what comes back in the response
The headers of a regular (non-streaming) HTTP response carry two fields worth logging on your side instead of recalculating manually:
X-Zukko-Cost-Micros— the cost of this specific request in microdollars (1,000,000 micros = $1);X-Fetch-Balance-Cents— the balance remaining after the charge; despite the name, also in microdollars (1,000,000 = $1), so a value of187654is about $0.19, not $1876.54.
Streamed responses (stream: true) don't carry these headers — check their spend in the dashboard, /v1/usage or /v1/balance. The exact balance in cents is the balance_cents field in the /v1/balance response.
If you're calling the API from a script or an agent, reading these headers as you go is cheaper than polling a separate balance endpoint on a schedule:
curl https://api.zukko.pro/v1/balance \
-H "Authorization: Bearer fetch_your_key"The full history of charges, with cursor-based pagination (next_cursor), comes from /v1/usage — useful when you need to reconcile spend over a period rather than just check the current balance. Each record has created_at, model, input_tokens, output_tokens, total_tokens and cost_cents (fractional cents). The history covers the whole account: all keys together, with no per-key filter.
Idempotency protects against double charges
If the connection drops and you're not sure whether the request landed, don't guess and retry blindly. Send an Idempotency-Key header with a unique value per logical request (and the same value on its retries — never regenerate it when retrying): retrying with the same key within 24 hours returns the same stored response and doesn't charge twice. If a different request body arrives with the same Idempotency-Key, the gateway responds with 422 idempotency_conflict — a guard against accidental mix-ups, not an arbitrary restriction.
A full cycle, end to end
curl -i https://api.zukko.pro/v1/messages \
-H "Authorization: Bearer fetch_your_key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "Idempotency-Key: session-42-step-1" \
-d '{"model":"claude-sonnet-5","max_tokens":200,"messages":[{"role":"user","content":"Explain the difference between TCP and UDP in two sentences"}]}'The -i flag prints response headers along with the body — that's where the price of this exact request and the remaining balance show up. For a full catalog of models with limits and current prices, without needing a key at all, there's a dedicated public endpoint, /v1/models/info; the plain model list without details is GET /v1/models.
The whole chain — counting input tokens, checking balance and key limits, checking the cache, calling the model, counting output tokens, deducting the charge, and writing it into both the response headers and the /v1/usage history — takes milliseconds and works the same way whether you go through the Anthropic-compatible /v1/messages path or the OpenAI-compatible /v1/chat/completions and /v1/responses. Image models use a different path, /v1/images/generations (image generation is temporarily unavailable right now), but the balance and key-limit checks before generation starts follow the same logic.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Buying AI tokens: how a dollar balance works and what $5 gets youTokens at a gateway are not a package but a dollar balance. We look at what is charged to it and calculate how many requests $5 covers.
- Paying for AI without a fee: is it possible and how to calculate the overpaymentThere is almost always a fee when you top up. We show what it consists of, calculate it for SBP and card, and give a formula for how many rubles you need for N dollars.
- Paying for AI in rubles: SBP, a dollar balance and the exchange rateRubles paid via SBP turn into a dollar balance at the rate on the payment date plus the payment page fee. The formula, a top-up table and the price of requests in rubles.
- "Your card was declined" in OpenRouter: why a card gets rejectedWhy a card payment can be rejected on a foreign service, what you can check yourself and how to connect models when payment does not go through.