---
title: "Estimate cost before a run"
description: "Size a translation run before you spend: count the keys with a dry run, let verbatra turn them into requests, tokens and money, then cap the run you agreed to."
type: "how-to"
---
# Estimate cost before a run

Size a translation run before you spend: count the keys with a dry run, let verbatra turn them into requests, tokens and money, then cap the run you agreed to.

Use this before a first run on a real project, or before any run someone has to approve. verbatra publishes no prices, because rates belong to the provider and change without notice. It counts what you will be billed for, and turns that into money once you give it your provider's current rates.

For a typical project, the first run is the one that costs anything, and the next run costs almost nothing because only changed keys are sent.

## Count the work [#count-the-work]

```bash
verbatra translate --dry-run
```

```text output
verbatra translate (dry run)
  de: 400 would translate, 0 unchanged
  es: 400 would translate, 0 unchanged
  fr: 400 would translate, 0 unchanged
3 succeeded, 0 partial, 0 failed (dry run: nothing written)
```

`would translate` is the number of keys a real run would send: those missing from the target file plus those whose source text changed. A dry run constructs no provider, reads no key and writes nothing. Treat the count as an upper bound: it skips [the cache](https://verbatra.kreitz-webdev.de/docs/the-cache.md), which serves repeat strings for free on a real run, and it does not merge identical source strings, which a real run sends once.

## Estimate the spend [#estimate-the-spend]

<AvailableFrom version="0.11.0" />

```bash
verbatra translate --estimate
```

```text output
verbatra translate (estimate)
  de: 400 would translate, 0 unchanged
  es: 400 would translate, 0 unchanged
  fr: 400 would translate, 0 unchanged
  estimate: 1200 keys in 24 requests, ~40944 input + ~30720 output tokens
  estimated spend: no rate on file for gemini/gemini-2.5-flash; add rates.table["gemini/gemini-2.5-flash"] to your config to see a currency figure
  estimate excludes: cache hits, duplicate source strings, provider-side retries, translation length, tokenizer differences, repair requests
3 succeeded, 0 partial, 0 failed (dry run: nothing written)
```

`--estimate` implies `--dry-run`, so it spends nothing. It reports the unit your provider bills: tokens for an LLM, source characters for DeepL and Google Cloud Translation, and no API cost for a self-hosted `openai-compatible` or `libretranslate` server. With `--json`, the same figures sit under `result.estimate` per locale and in total, so a script can gate on them.

To see money, look up your model's current rate on the provider's own pricing page:

| Provider | Billed by | Pricing |
| --- | --- | --- |
| Gemini | input and output tokens (free tier available) | [Gemini API pricing](https://ai.google.dev/gemini-api/docs/pricing) |
| Anthropic | input and output tokens | [Anthropic pricing](https://platform.claude.com/docs/en/about-claude/pricing) |
| OpenAI | input and output tokens | [OpenAI API pricing](https://developers.openai.com/api/docs/pricing) |
| OpenCode | input and output tokens | [OpenCode Zen pricing](https://opencode.ai/docs/zen/) |
| DeepL | source characters | [DeepL API pricing](https://www.deepl.com/pro#api) |
| Google Cloud Translation | source characters | [Cloud Translation pricing](https://cloud.google.com/products/translate/pricing) |
| openai-compatible, LibreTranslate | nothing: your own server | no API cost |

Then add it to the config with the date you read it:

```ts title="verbatra.config.ts"
export default defineConfig({
  // ...
  rates: {
    asOf: "2026-01-15",
    currency: "USD",
    table: {
      // placeholders: copy the current rates from https://ai.google.dev/gemini-api/docs/pricing
      "gemini/gemini-2.5-flash": { inputPerMillionTokens: 1, outputPerMillionTokens: 2 },
    },
  },
});
```

The rates above are placeholders, not quoted prices. Every figure printed from them carries the `asOf` date, so an old rate card is visibly old. verbatra never invents a missing rate or prints `0.00` for a model it cannot price; it names the missing entry instead. See [`rates`](https://verbatra.kreitz-webdev.de/docs/config-file.md#rates) for the key format and units.

## Know what the figure covers [#know-what-the-figure-covers]

The estimate performs the arithmetic you would otherwise do by hand:

- **Requests.** Each locale's keys are split into batches of at most [`maxBatchSize`](https://verbatra.kreitz-webdev.de/docs/config-file.md#maxbatchsize) (default `50`), one request each, plus the batches [`generatePlurals`](https://verbatra.kreitz-webdev.de/docs/config-file.md#generateplurals) would send.
- **Input.** Every LLM request carries fixed system rules (about 550 tokens) and a response schema (about 100 tokens), then the keys, their descriptions, the tone, and the target locale's full glossary. The prompt is measured by serializing the payload that would be sent, so a large glossary is counted on every request it rides on.
- **Output.** A translation does not exist yet, so it is sized as the source text plus 50 percent, since German, French and Russian often run a third to a half longer than English.
- **Tokens.** Characters are converted at about four per token, which fits English prose; other scripts run denser.

A real run usually costs less, for the two reasons the dry-run count is an upper bound. It can also cost more, and the `estimate excludes` line names each way. Two matter most:

- **Retries.** A failed request is [retried](https://verbatra.kreitz-webdev.de/docs/providers.md#retries): up to two extra attempts for Anthropic, OpenAI, OpenCode, `openai-compatible` and Gemini, five for DeepL, none for Google Cloud Translation and LibreTranslate. An attempt that reached the model is billed.
- **Repairs.** When keys come back missing, verbatra sends one repair request, which carries the system rules, glossary and tone again in full.

Plan with the figure; do not promise it as a ceiling. The estimate also covers `translate` only: retranslating one entry from [Studio](https://verbatra.kreitz-webdev.de/docs/review-in-studio.md) or an agent tool spends on its own path.

## Cap the run [#cap-the-run]

<AvailableFrom version="0.12.0" />

Give the run you agreed to a hard stop a little above the estimate's input plus output tokens:

```bash
verbatra translate --max-tokens 80000
```

A request that would pass the ceiling is withheld rather than sent, and its keys stay pending for the next run. When the config also sets `maxTokens`, the lower figure applies. DeepL, Google Cloud Translation and LibreTranslate report no tokens, so the ceiling is enforced against verbatra's own projection for them. See [Token ceiling](https://verbatra.kreitz-webdev.de/docs/cli/translate.md#token-ceiling), and [`maxTokens` and `budgetBehavior`](https://verbatra.kreitz-webdev.de/docs/config-file.md#maxtokens-and-budgetbehavior) for a budget that applies to every run.

## Calibrate on one locale [#calibrate-on-one-locale]

The precise number comes from a real run. Translate one locale and read the usage:

```bash
verbatra translate --locales de
```

```text output
verbatra translate
  de: 400 translated, 0 unchanged, 18400 tokens (10800 in, 7600 out)
  total: 18400 tokens (10800 in, 7600 out)
1 succeeded, 0 partial, 0 failed
```

Multiply by the number of remaining locales. With `--json`, the figures are `usage.inputTokens` and `usage.outputTokens`. The run spends real money, but the work is kept: that locale is done.

## Read what past runs cost [#read-what-past-runs-cost]

<AvailableFrom version="0.7.0" pkg="@verbatra/studio" />

Studio's Overview lists recorded runs with their cost, frozen when the run ended, so a later change to `rates` never rewrites it. An amount is the tokens the provider reported times the rate on file at that time. **Estimated** is a source-characters figure for a provider that reports no usage. **At least** is a lower bound, because a locale reported no tokens. **No rate** means no rate matched that model, and **unknown** means keys were sent but no tokens came back; neither is free. Self-hosted providers read **not billed**. The record is documented under [`runHistory`](https://verbatra.kreitz-webdev.de/docs/sdk/history.md#runhistory); [Review in Studio](https://verbatra.kreitz-webdev.de/docs/review-in-studio.md) shows the page.

## Machine-translation providers [#machine-translation-providers]

DeepL and Google Cloud Translation bill source characters, not tokens, and LibreTranslate counts source characters without billing anything. verbatra sends them the source text with placeholders as short numbered markers, and no system rules, key names or JSON around it. The estimate counts source characters only. DeepL does not bill the tags around its markers; whether Google bills the span markup around them is not documented. A value these providers withhold ([`PLACEHOLDER_UNSUPPORTED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#placeholder_unsupported)) is not sent, so the characters sent can be fewer than the raw total.

### Google Cloud Translation [#google-cloud-translation]

<AvailableFrom version="0.10.0" />

Google Cloud Translation is counted like DeepL: source characters in the estimate, and a projected token figure for a `maxTokens` budget, marked as estimated on the run summary.

## Estimate from an agent [#estimate-from-an-agent]

Two agent tools return what `translate --estimate --json` prints, for all locales or a subset, and never call a provider:

| Tool | Where |
| --- | --- |
| `translation.estimate` | the [MCP server](https://verbatra.kreitz-webdev.de/docs/cli/mcp.md) <AvailableFrom version="0.3.0" pkg="@verbatra/mcp" /> |
| `verbatra_translation_estimate` | [Studio's agent tools](https://verbatra.kreitz-webdev.de/docs/agent-tools-in-studio.md) <AvailableFrom version="0.6.0" pkg="@verbatra/studio" /> |

### Spend within the estimate [#spend-within-the-estimate]

<AvailableFrom version="0.3.0" pkg="@verbatra/mcp" />

`translation.translatePending` then takes the same `locales` and a `maxTokens` ceiling. See [Estimate, then spend](https://verbatra.kreitz-webdev.de/docs/cli/mcp.md#estimate-then-spend).

## Why the next run costs less [#why-the-next-run-costs-less]

The figures above are the first-run cost. After that, verbatra sends only keys that are missing or whose source changed since [the lock file](https://verbatra.kreitz-webdev.de/docs/the-lock-file.md) baseline, and [the cache](https://verbatra.kreitz-webdev.de/docs/the-cache.md) reuses earlier translations of identical strings. Day to day you pay for the strings you edited. To rehearse for free, use Gemini's free tier or point `openai-compatible` at a local model; see [Providers](https://verbatra.kreitz-webdev.de/docs/providers.md).

## Related [#related]

- [`verbatra translate`](https://verbatra.kreitz-webdev.de/docs/cli/translate.md): `--dry-run`, `--estimate`, `--max-tokens` and `--locales`.
- [The config file](https://verbatra.kreitz-webdev.de/docs/config-file.md#rates): `rates`, `maxTokens`, `budgetBehavior` and `maxBatchSize`.
- [Providers](https://verbatra.kreitz-webdev.de/docs/providers.md): every provider's options and where to get a key.
