> ## Documentation Index
> Fetch the complete documentation index at: https://ctrlrun.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# CTRLRun and idempotency keys

> An idempotency key deduplicates at one API that chose to support it. An effect key deduplicates at the agent, across every API it touches.

An idempotency key is a header one API honours so that the same request sent twice charges once.
An effect key is the name of a consequence on the agent's side, enforced before the call goes
out, across every API the agent touches, whether or not any of them support idempotency. Where
an API does support it, pass the effect key as the idempotency key and get both.

## What idempotency keys are good at

At the remote, they are the strongest thing available: the provider itself knows whether it has
seen the key, and can return the original response rather than acting again. Stripe's is the
canonical example and it works. If every call your agent makes is to one API that implements
them well, you have solved a large part of this problem.

## What they do not do

|                                       | Idempotency keys                                       | Effect keys                                                                  |
| ------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------- |
| Where enforced                        | at the remote, if it supports them                     | at the agent, before the call                                                |
| Coverage                              | that one API                                           | every remote, including ones with no such feature                            |
| Two different APIs, one consequence   | two unrelated keys                                     | one effect key, one reservation                                              |
| Two agents, same intent, same instant | both requests reach the remote; the remote resolves it | one reserves, the other is refused before the call                           |
| An unknown outcome                    | resend with the key and hope the remote deduplicates   | `AMBIGUOUS`: the retry is refused until a human or a hook says what happened |
| Retention                             | the provider's window, often a day                     | your store, as long as you keep it                                           |
| Bound to an approval                  | no                                                     | yes: the same atomic write consumes the approval and reserves the key        |
| Bound to authority                    | no                                                     | yes: authority is evaluated before either                                    |
| Evidence                              | the provider's dashboard                               | a receipt naming the key, the decision, the approver and the outcome         |

The row that matters most is the unknown outcome. Resending with an idempotency key is the right
move when the remote implements them and the window has not passed. It is a guess when the
remote does not, when the call went to a second API, when the window expired, or when the client
never learned whether the first request arrived. CTRLRun refuses to guess and makes someone
look.

## When to use both

Always, where the remote supports it. Pass the effect key as the idempotency key:

```python theme={null}
@ctrlrun.protect("stripe.refund", effect="refund:{payment_id}")
def refund(payment_id: str, amount: int) -> dict:
    return stripe.Refund.create(
        payment_intent=payment_id,
        amount=amount,
        idempotency_key=f"refund:{payment_id}",
    )
```

Now the agent refuses the second attempt before it leaves, and the remote refuses it if one ever
arrives. Two independent defences, and the receipt says which one acted.

## The distinction that matters

An idempotency key makes *a request* repeatable. An effect key makes *a consequence*
identifiable, before anything is sent, across the whole agent, and joined to who approved it and
what was recorded.

## Next

* [Effect keys](/concepts/effect-keys): the definitional page.
* [Compare: durable workflows](/compare/durable-workflows).
* [Get started](/get-started/quickstart) · [Why](/why).


## Related topics

- [CTRLRun and durable workflow engines](/compare/durable-workflows.md)
- [Effect keys](/concepts/effect-keys.md)
- [Frequently asked questions](/faq.md)
- [Architecture](/ARCHITECTURE.md)
- [Not only agents](/not-only-agents.md)
