Idempotency-Key: retrying without paying twice

by CIA updated 21/09/2026 3 min read

How an Idempotency-Key prevents double charging

Also available in: Tiếng Việt

A network failure between the request reaching the server and the response coming back happens routinely. When it does, your code does not know whether the run happened, so it retries. Without an Idempotency-Key, that retry is a new run and is charged again.

How the key works

You send a self-generated string in the Idempotency-Key header. The system stores that key with the run's result, unique per user and key.

Resend the same key and the system returns the earlier result and does not charge again. Send a different key and it is a new run, charged as such.

So the rule is simple: one intention = one key. Retrying keeps the key; wanting a fresh run changes it.

Generating keys correctly

The key should map to your intention, not to the moment of the call. Three workable approaches:

A UUID generated once and kept. Generate before calling, store it in a variable or in your database, then use it for every retry of that same intention.

A hash of the parameters plus a time bucket you choose. For example a hash of "channel report for X, 21/09/2026". The same intention produces the same key.

A record ID in your own system. If each run corresponds to a row in your database, use that row's ID.

Three common mistakes

1. Generating a new key inside the retry loop. This defeats the key entirely. Generate before the loop.

2. Using a timestamp as the key. Each retry has a different timestamp, so each retry is a new run.

3. Reusing one key for a new intention. If you want to re-run the same channel next week for fresh data, you must change the key — otherwise you receive last week's result.

The third mistake is the inverse of the first two and also causes confusion: users think the data is stale when in fact they asked for exactly the old result.

The correct pattern, in words

key = generate_uuid()          # ONCE, outside the loop
attempt = 0
loop:
    result = call_api(params, headers={"Idempotency-Key": key})
    if success: break
    if temporary error and attempt < 3:
        attempt += 1
        wait (1s, 4s, 9s)
        continue               # KEEP the same key
    else: report failure

The three increasing waits are ordinary backoff. What matters is that key does not change throughout the loop.

With background tools

Slow tools return HTTP 202 with a run identifier rather than the result. Idempotency-Key still behaves as above: resending the same key returns that same run rather than creating a new one. See getting started with the API.

Frequently asked questions

Do keys expire? A key is unique per user and key. Do not rely on expiry; change the key when you want a new run.

What if two users send the same key? No conflict. Uniqueness is on the user-and-key pair.

Is sending the key mandatory? Not mandatory, but without it every retry is charged.

More on this topic: Data & API