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

# Idempotency

> Why every money-moving create requires a key, what a reused key does, and how to choose one.

Every create that moves money requires an `Idempotency-Key` header. This is not advisory. A request without one is rejected.

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/payments \
  -u "$APP_ID:$APP_SECRET" \
  -H "Idempotency-Key: 0b5a4a2e-2c3f-4a1e-9f6b-5f2d1f8a7c31" \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "send",
    "sender": { "accountId": "acct_123" },
    "destination": { "type": "payment_handle", "paymentHandle": "alice@partner" },
    "paymentAmount": { "amount": "50.00", "assetCode": "USDC" }
  }'
```

## The problem it solves

A request times out. You do not know whether it arrived. Retrying risks a second payment; not retrying risks none. Without a key there is no safe answer.

With a key there is: **retry the identical request with the same key.** If the first attempt landed, you get that same result back rather than a second movement.

## The three outcomes

| You send | Stableyard does |
| - | - |
| A new key | Processes the request normally |
| The same key, the same body | Returns the original result. Nothing moves twice |
| The same key, a **different** body | Refuses with `idempotency_conflict` (409) |

The third case is the important one. The key is bound to the exact input, so it cannot be recycled across different operations. If you change an amount, a destination or an account, you need a new key.

While a request is still being processed, an identical retry returns `request_in_progress` (409). Wait and read the resource rather than retrying immediately.

## Choosing a key

Check the operation's header schema for its limits. Funding-account issuance (`POST /v2/accounts/{accountId}/onramp-bank-accounts`) requires 8–256 printable characters after trimming surrounding whitespace. A stable namespaced key such as `bank-funding:acct_123:v1` meets that requirement.

* **Derive it from your own operation**, not from a random value generated at call time. A UUID created inside your retry loop changes on every attempt, which defeats the purpose.
* **A good key is your order id, invoice id or payout batch id**, namespaced by operation: `payout:invoice_8891`. It survives a process restart and is the same on every retry.
* **Do not reuse a key across environments.** Keys are scoped per app environment.

<Warning>
  Generating the key at the moment of sending is the most common mistake. If a timeout triggers a retry with a fresh key, you have created a second payment.
</Warning>

## Where keys are required

The key is required on these creates, and a call without one is refused:

| Call | Endpoint |
| - | - |
| Create an account | `POST /v2/accounts` |
| Create a payment | `POST /v2/payments` |
| Link a bank beneficiary | `POST /v2/accounts/{accountId}/bank-accounts` |
| Link a rail identifier | `POST /v2/accounts/{accountId}/payment-rail-identifiers` |
| Issue a bank funding account | `POST /v2/accounts/{accountId}/onramp-bank-accounts` |
| Create a refund | `POST /v2/payments/{paymentId}/refunds` |
| Start email verification | `POST /v2/accounts/{accountId}/email/verification-challenges` |
| Activate a capability | `POST /v2/accounts/{accountId}/capability-activations` |
| Vault operations | Create a Vault, propose, authorize or resend a policy, complete an action, create or revoke a mandate |

Bank linking and rail-identifier keys must contain 1–256 characters after trimming surrounding whitespace.

The key is optional, and honoured when sent, on `POST .../deposit-addresses`. Send one anyway: it is what makes a timed-out retry safe. Outgoing Payment confirmation uses the pending `actionId` and proof rather than an idempotency key.

Reads never require a key, and sending one has no effect.

## What a key does not do

* It does not make an operation reversible. It prevents duplication, not commitment.
* It does not extend across resources. A key used for a payment has no bearing on a later refund of that payment.
* It does not replace reading state. After a retry returns, read the resource before acting on it.

## Next

<CardGroup cols={2}>
  <Card title="Errors" icon="triangle-exclamation" href="/errors">
    Every error code, and which are safe to retry.
  </Card>

  <Card title="Status codes" icon="list-check" href="/status-codes">
    What each state means once the request has landed.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.