# Estimate LLM cost before the request, not after the bill

URL: https://infro.io/blog/estimate-llm-cost-before-request
Author: INFRO Editorial (https://infro.io/blog/editorial)
Publisher: INFRO
Published: 2026-10-13
Last modified: 2026-10-13
Last reviewed: 2026-10-13 (next review due 2026-11-12)
Topic: Cost and FinOps
Format: Guide

> Estimate a request's provider cost from what you know before the call: counted input tokens and the output ceiling for text, image count with resolution and quality, video seconds at a resolution, characters or audio minutes for speech. Text is always a range, because output and reasoning tokens are unknown until the model stops; use the worst case for budget admission, the expected figure for previews, and say unpriced rather than $0 when no rate is known.

Key takeaways:
- Split every request into what is known (input, image count, video seconds), what is bounded (output tokens under max_tokens) and what is unknown (reasoning length, self-chosen durations).
- Count input tokens with the target model's own tokenizer; counts move between model generations.
- Reasoning tokens are billed as output and are not visible, so the only safe text bound is the output ceiling you set.
- Use the worst case to admit a request under a budget, the expected figure to preview cost to a user, and recorded costs to compare models.
- When no rate is known the answer is unpriced, never $0, and a negotiated rate beats the list rate whenever you have one.

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](https://infro.io/blog/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_tokens` or `max_output_tokens` ceiling.
- **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](https://infro.io/blog/tokens-are-not-the-billing-unit).

[Diagram: What a pre-request estimate can rely on, by modality. Units vary by vendor; check the rate card of the provider that will serve the request.]

## 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 amount[1]. 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 counts[1]. 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 afterwards[2]. 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 answer[2]. 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](https://infro.io/blog/reasoning-tokens-cost) covers how to size it.

> **Example: One text request as a range**
> A support answer with 2,400 counted input tokens and an 8,000-token output ceiling, on a model billed at an illustrative $3 per million input tokens and $15 per million output tokens.
> 
> - **Low**, a short answer with no reasoning: 200 output tokens. 2,400 × $3/M + 200 × $15/M = $0.0072 + $0.0030 = **$0.0102**.
> - **Expected**, the median of past runs: 600 visible plus 1,200 reasoning tokens. $0.0072 + 1,800 × $15/M = **$0.0342**.
> - **Worst case**, the ceiling reached: $0.0072 + 8,000 × $15/M = **$0.1272**.
> 
> The worst case is twelve times the low figure, and the difference is almost entirely output the request may never produce.

[Diagram: The same request at three points of its range, in tokens. Input is fixed; output and reasoning move. Illustrative figures from the example above.]

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 output[3]. OpenAI prices its image models per million image and text tokens and points to a calculator in its image generation guide for estimates[4]. 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 successfully[3]. 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 tokens[4]; Google bills Gemini TTS audio output per million tokens at 25 tokens per second of audio[3]. 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 audio[4], 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](https://infro.io/blog/negotiated-rates-list-price-drift) covers how that drift appears and how to find it.

## Which number each decision needs

[Diagram: Three decisions, three different numbers, and one rule for when there is none.]

- **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.

```bash
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"}'
```

```json
{
  "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 }
  }
}
```

[Diagram: What POST /v1/estimates takes and returns, from the gateway source. The provider is never called.]

`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.

[Diagram: The order in which an INFRO estimate looks for a 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](https://infro.io/docs/org/spend-controls).

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](https://infro.io/docs/org/billing).

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.

## Sources

1. Token counting — Anthropic. https://platform.claude.com/docs/en/build-with-claude/token-counting (accessed 2026-10-13)
2. Reasoning models — OpenAI. https://developers.openai.com/api/docs/guides/reasoning (accessed 2026-10-13)
3. Gemini Developer API pricing — Google. https://ai.google.dev/gemini-api/docs/pricing (accessed 2026-10-13)
4. Pricing — OpenAI. https://developers.openai.com/api/docs/pricing (accessed 2026-10-13)
