# Your agent retried, and you paid twice

> The same file processed again is the same money again. Replay is keyed on the content, so a repeat is recognised as a repeat.

Canonical HTML version: https://mcpbelt.com/problems/do-not-pay-twice-for-the-same-file

## The problem

An agent that times out retries, because that is what a well-behaved agent does. A workflow that runs on a schedule sends yesterday's file again. A person uploads the same contract under a slightly different name because they could not remember whether they had already done it. None of these is a bug.

On a metered API every one of them is a second charge for a result you already have. The first call may even have succeeded: the answer got lost on the way back, the connection dropped, the client gave up one second before the response landed. The work was done and paid for, and it is about to be done and paid for again.

The version that stings is the concurrent one. The retry arrives while the first call is still running, so there is nothing finished to hand back yet and nothing that looks like a duplicate. Two engines are now processing the same file, and both invoices are real.

## Why it fails locally

The instinct is a cache in front of the call, and it solves the easy case while getting the one that matters wrong. A cache keyed on a filename misses the same document saved twice under two names, and hits on two different documents that happen to share one. Keyed on the request parameters, it misses the retry that resent the file rather than the reference.

The deeper problem is that a cache and a ledger are **two separate records of the same money**. They will agree for months and then they will not: an eviction, a partial write, a deploy that clears one and not the other, and now the answer to whether you were charged depends on which system you ask. Reconciling those after the fact is an evening you do not get back.

And a cache does not help with the concurrent retry at all, because at the moment the second call arrives there is no entry to hit. Stopping that one needs a lock taken before the work starts, not a lookup performed after it finishes.

## The code

The same file read twice. The second call returns the first result: `cached` is true and `cost_eur` is zero. Nothing had to be configured for this and there is no cache to enable.

```
read(key="<key>", mode="balanced")
  -> handle, engine, cost_eur, cached: false

# the same bytes again, under any key or filename
read(key="<key-of-the-same-file>", mode="balanced")
  -> handle, engine, cost_eur: 0, cached: true

# a retry that arrives while the first call is still
# running is refused before an engine is paid, not
# after. Ask the handle instead of starting again:
result(handle="<handle>")
  -> status: running, retry_after_seconds
```

## What it costs

A replay costs nothing. `cost_eur` is zero and `cached` is true, and that is the whole of it: no reduced rate, no discount tier, no charge for the lookup. A first `read` in `balanced` mode is 0.007 EUR per page, and reading the same file again is free.

Two cases that look like one. If the stored result is still there, it is handed back and nothing is charged. If it has expired, the file is processed again and you are charged again, because an engine really was paid for the second time. `cached` tells you which of the two happened, on every call.

The identity is computed on the content, so the same bytes uploaded under a new name are the same job. It is not a cache sitting next to the billing: it is the same key the billing uses, which is why the two cannot disagree. You start with 0.10 EUR of trial credit and top up from 5 EUR: see [pricing](/pricing).

## What this does not do

- Replay is per account. Another account reading the same document is a new job at the normal price.
- The same file in a different mode is a different job, because it is different work: `balanced` and `exact` do not call the same engines.
- Stored results expire. After that the handle stops answering and the next read is charged in full.
- If the process dies between the engine answering and the charge being written, the retry calls the engine again. That window is narrow and it is real, and the number of retries is capped rather than left to run.
