Every AI provider bills after the work is done. The decisions that cost money are made before it: whether a request fits under a budget, what a button in your product should say it costs, which model an experiment should try. Those need an estimate of the provider's charge before the call, and an estimate that is honest about what it does not know. This guide covers how to build one for text, images, video and audio, why the text estimate is always a range, and which number each decision needs. For how providers price in the first place, read AI API pricing explained; this article starts where that one ends.
Known, bounded and unknown
Every request can be split into three kinds of quantity:
- Known before the call. The input you are about to send, the number of images you ask for, the seconds of video, the characters of a speech script, the length of an audio file to transcribe.
- Bounded. Quantities you cannot know but can cap, chiefly output tokens under a
max_tokensormax_output_tokensceiling. - Unknown. Quantities with no cap you control: how much a reasoning model thinks inside its ceiling, how long a video model runs when it chooses its own duration.
An estimate multiplies the known and bounded quantities by the rate for the unit the provider bills in, and says plainly which part is a ceiling. The unit itself differs by provider and modality, which is the subject of tokens are not the billing unit.
| What is known before the call | Billed per | Known before the call | Unknown until it finishes |
|---|---|---|---|
| Text | Input and output token (some vendors: cached and cache-write input) | Input tokens, if you count them with the model's own tokenizer | Output length, including reasoning tokens; only the max_tokens ceiling is known |
| Image | Image, by resolution and quality tier (some vendors bill image tokens) | Count, resolution and quality you ask for | ◐a failed or retried generation, edits with input images |
| Video | Second of output, by resolution; audio may be included or separate | Duration and resolution you ask for | Models that choose their own length |
| Speech | Character of input text, or audio tokens | Characters you send | Audio-token models: output length |
| Transcription | Minute or hour of audio | The file's duration | Little; this is the most predictable unit |
Text: count the input, bound the output
Input is knowable, provided you count it with the right tokenizer. Providers expose counting endpoints for this. Anthropic's token counting endpoint takes the same structure as a message, including system prompt, tools, images and PDFs, and returns the input token count; it is free to use, rate-limited separately from message creation, and Anthropic calls the result an estimate that can differ from the billed count by a small amount1. Anthropic also notes that its newer models use a tokenizer that produces roughly 30% more tokens for the same text than earlier models, and tells you to recount prompts against the model you plan to use rather than reuse old counts1. The general lesson holds for every vendor: a count is per model, and a cached count from last quarter's model is a wrong count.
Count everything the provider bills as input: the system prompt, every message in the history, tool definitions and schemas, retrieved documents and images. A retrieval step that adds ten passages changes the estimate more than the user's question does.
Output is not knowable, only boundable. The ceiling you set is the bound. Without one, the bound is the model's maximum output, which is usually far above anything the request needs and makes the worst case useless.
Reasoning tokens sit inside that ceiling and make the range wider. OpenAI's reasoning guide states that reasoning tokens are not visible through the API, occupy context, and are billed as output tokens; max_output_tokens limits the total including reasoning; and the response's usage object reports the exact count afterwards2. OpenAI recommends reserving at least 25,000 tokens for reasoning and output when you start experimenting, and warns that a request which reaches the limit returns incomplete, possibly before any visible output, so you can pay for input and reasoning and get no answer2. That makes the ceiling a cost control and a correctness risk at once: set it too low and you pay for nothing usable. The reasoning tokens article covers how to size it.
- Input (counted)
- Visible output
- Reasoning
- tokens
- Low (short answer)2,600 tokens
- Expected (p50 of past runs)4,200 tokens
- Worst case (max_tokens 8,000)10,400 tokens
Two refinements tighten text estimates. Use the dearest input rate the request could incur when you need a bound: a prompt-cache write is billed above the plain input rate at some vendors, so a bound priced at plain input is not a bound. And for an expected figure, use your own history: the median output of the same workflow over the last few weeks is a better predictor than any rule of thumb.
Images: count, resolution and quality
Image generation is easier because the output is chosen in the request. The quantities are the number of images, the resolution and, where offered, a quality setting. Google prices image output from its Gemini image models per image by resolution; Gemini 3.1 Flash Image, for example, lists separate per-image prices for 0.5K, 1K, 2K and 4K output3. OpenAI prices its image models per million image and text tokens and points to a calculator in its image generation guide for estimates4. The same picture is therefore a per-image figure at one vendor and a token figure at another; normalize to cost per image at the resolution and quality you ask for.
The estimate can still miss. An edit that sends input images adds input cost, and a request that fails and is retried may bill twice depending on the provider. Price the request as asked, and record what was actually billed.
Video: seconds, resolution and audio
Video is billed per second of output, and the rate depends on the model tier and the resolution. Google's Gemini API lists Veo 3.1 Fast at $0.10 per second at 720p, $0.12 at 1080p and $0.30 at 4K, with audio included in the default price, and says you are charged only when the video generates successfully3. An eight-second 1080p clip on that rate card is 8 × $0.12 = $0.96, which can be stated before the job starts.
Two things break that arithmetic. A model that chooses its own duration cannot be priced in advance; the honest answer is "unknown length" until the job finishes. And audio is priced into the base rate at some vendors and as a separate option at others, so the same flag can change the estimate at one provider and not at another.
Audio: characters and minutes
Speech synthesis is billed either per character of input or per audio token of output. OpenAI lists tts-1 per million characters and gpt-4o-mini-tts per million text and audio tokens4; Google bills Gemini TTS audio output per million tokens at 25 tokens per second of audio3. A character-priced model is exact before the call; a token-priced one depends on how long the voice takes to say it.
Transcription is the most predictable unit of all: OpenAI prices its transcription models per minute of audio4, and the file's duration is known before you send it.
Why the answer is a range, and sometimes no answer
A single figure for a text request is a promise the provider did not make. Show the expected figure with its ceiling, or the range, and say which is which.
Sometimes there is no figure. A new model with no published rate, a provider whose rate card does not cover the option you chose, or a self-chosen duration all leave the estimate unknown. Report that as unpriced, with the reason. Never substitute $0: a dashboard that adds unpriced requests as zero shows spend falling exactly when the riskiest traffic grows, and a budget that measures them as zero does not govern them.
List rate or your negotiated rate
Most estimates use the vendor's published list rate because it is the only one in the documentation. If your organization has a negotiated rate, a committed-use agreement, or buys a model through a cloud provider with its own rate card, the list rate is the wrong input and the estimate will drift from the invoice by exactly that difference. Enter the rate you actually pay, per model and per provider where they differ, and keep the list rate as the fallback. The negotiated rates article covers how that drift appears and how to find it.
Which number each decision needs
Which number does this decision need?
- Admitting a request under a blocking budgetThe worst case: input at the dearest input rate, output at the full ceiling, the dearest fallback target
- Showing a user what an action will costThe expected figure, with the range or a ceiling beside it
- Choosing a model in an experimentThe recorded cost of real runs, compared on the same inputs
- No rate is published for the modelUnpriced: say so, never show $0
- Budget admission needs the worst case. A gate that admits a request on its expected cost will be crossed by the requests that run long. Price input at the dearest rate it could incur, output at the full ceiling, and, if the request can fail over, at the dearest target it could reach.
- Cost previews in a product need the expected figure, with the ceiling or range beside it when the spread is large. A preview that shows the worst case for every text request trains users to ignore it.
- Experiments need recorded costs, not estimates. When you compare models, run the same inputs through each and compare what each provider reports, because output length differs by model and that is most of the cost.
Estimates in INFRO: POST /v1/estimates, the Playground and your rates
INFRO is a control plane in front of the provider accounts you already hold. It takes 0% markup on model spend; every cost figure it shows is an estimate of what your own provider will bill you, and INFRO's subscription is never part of it.
POST /v1/estimates answers what a request would cost on your own provider account before you make it. It takes model, an optional provider (otherwise the first of your connections that would serve the model), and the quantities: n, size or resolution and quality for images, duration_seconds for video and transcription, characters for speech, input_tokens and output_tokens for text. The request is checked against your model allowlists, and no provider is called.
curl https://api.infro.io/v1/estimates \
-H "Authorization: Bearer $INFRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "google/veo-3.1-fast", "provider": "google", "duration_seconds": 8, "resolution": "1080p"}'{
"object": "estimate",
"model": "google/veo-3.1-fast",
"provider": "google",
"estimated_cost": 0.96,
"unpriced_reason": null,
"price_source": "list",
"rate": {
"unit": "second",
"unit_cost": 0.1,
"tiers": { "720p": 0.1, "1080p": 0.12 }
}
}POST /v1/estimates
You sendmodel, optional provider, and the quantities: n, size or resolution and quality, duration_seconds, characters, input_tokens and output_tokens.no provider call
INFRO resolvesChecks the model against your allowlists, picks the named provider or the first of your connections that serves the model, and finds the rate.object: estimate
You get backestimated_cost in USD or null, unpriced_reason, price_source, and the rate: unit, unit_cost, tiers, and per-million-token rates for text.authoritative
Your provider billsThe estimate is what your provider is expected to bill your account. INFRO's subscription is never part of it.
estimated_cost is in US dollars, or null with an unpriced_reason: no_rate when nobody has published a rate, unknown_length when a video or transcription request names no duration, unknown_quantity when a text or speech request names no quantity. For a text model, rate also carries input_per_1m_tokens and output_per_1m_tokens. The text estimate is input times the input rate plus output times the output rate, so the number you pass as output_tokens decides which point of the range you get: pass your ceiling for the worst case, your typical output for the expected figure. Units are billed per started unit, as the gateway records them, so 7.5 seconds is priced as 8.
The rate comes from your own negotiated rate for that provider where you have entered one, then your rate for the model on any provider, then the model vendor's list rate; price_source reports which (custom_provider, custom, list). Resolution and quality tiers apply to a list rate only; a negotiated rate is used as you entered it. Entering negotiated rates under Prices in the console is included from the Team plan; on other plans estimates use list rates.
1 · custom_provider
Your rate for this provider
A negotiated rate entered for this model on the provider that would serve it.
2 · custom
Your rate for the model
A negotiated rate entered for the model without naming a provider.
3 · list
Vendor list rate
The model vendor's published rate, with its resolution or quality tier where the vendor lists one.
4 · null
Unpriced
No rate is known: estimated_cost is null with unpriced_reason no_rate.
The same arithmetic runs inside the gateway. Under a blocking budget, a request is admitted only if its worst-case estimated cost fits the room left: the whole billed request body, every requested choice, the most expensive input rate the model could apply, and the most expensive model and provider it could fall back to. When a chat request sets no max_tokens, the worst case assumes the model's maximum output, 32,768 tokens where the catalog lists none, and INFRO sends that ceiling to the provider. Setting max_tokens yourself reserves less. A request that could reach an unpriced model is refused with 402 unpriced_model_under_budget by default, because its worst case is unknown. The details are in the spend controls docs.
After a request, every response carries usage.cost and usage.price_source, and the request log records the estimated cost per request, or null when the model is unpriced, as described in the billing docs.
The console Playground runs text, image, video, speech and transcription on one of your connections or through a gateway key. After each run it shows the total time and, for text, time to first token, tokens in and out and output speed, with the estimated provider cost, shown as "Unpriced" when no rate is known. Copy cURL copies the request as a call to /v1 with a gateway key, which makes the Playground a quick way to build the body you then pass to /v1/estimates. The Playground's history of your last twenty runs stays in your browser only; INFRO stores no prompt for it, while the request log keeps each run's record. INFRO Studio uses the same estimate to show the provider cost of every step before a run starts.
A checklist before you ship a cost estimate
- Input counted with the target model's own tokenizer, including tools, history and retrieved context.
- An explicit output ceiling on every text request that a budget governs.
- Reasoning models given enough ceiling to finish, and the ceiling priced as the worst case.
- Images priced per image at the resolution and quality requested; video per second at the resolution, with audio checked.
- Unpriced shown as unpriced, with its reason, never as $0.
- Negotiated rates entered wherever they apply, with the list rate as the fallback.
- Worst case for admission, expected figure for previews, recorded cost for experiments.
