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

# Idempotency

> Which create requests are safe to retry, and what we return when you retry them.

A network timeout leaves you unsure whether a create request landed. Each create request below has a rule for what happens when you send it again, so you can retry without creating a duplicate.

We read an `Idempotency-Key` header only on [payment account registration](#payment-accounts). On every other request it has no effect.

## Orders: keyed on quoteId

[`POST /organization/{organizationId}/order`](https://docs.etherfuse.com/v2/api-reference/orders/create-order) creates at most one order per quote. The order's `id` is the `quoteId`.

| Request | Response |
| - | - |
| First request for a `quoteId` | `201` with the new order. |
| Same `quoteId` again, after the order exists | `200` with the existing order, unchanged. We ignore a different `memo`. |
| Same `quoteId` again, after the quote expired | `200` with the existing order. We check for the order before we check the quote. |
| Two requests for the same `quoteId` at the same moment | One gets `201`. The other gets `409` `conflict`. |
| A `quoteId` that has no order and has expired | `400` `quote-expired`. |

So a timed-out request is safe to retry with the same `quoteId`:

1. Keep the `quoteId` from the quote response.
2. Retry `POST /organization/{organizationId}/order` with the same organization id and `quoteId`.
3. Treat `201` and `200` as success.
4. On `409` `conflict`, read the order with [`GET /organization/{organizationId}/order/{id}`](https://docs.etherfuse.com/v2/api-reference/orders/get-order), using the `quoteId` as the id.

```js theme={null}
async function createOrder(organizationId, quoteId) {
  const base = `https://api.sand.etherfuse.com/organization/${organizationId}`;
  const res = await fetch(`${base}/order`, {
    method: "POST",
    headers: { Authorization: apiKey, "Content-Type": "application/json" },
    body: JSON.stringify({ quoteId }),
  });

  if (res.status === 201 || res.status === 200) return res.json();

  const problem = await res.json();
  if (problem.type === "https://docs.etherfuse.com/ramp/v2/errors/conflict") {
    return fetch(`${base}/order/${quoteId}`, {
      headers: { Authorization: apiKey },
    }).then((r) => r.json());
  }
  throw new Error(`Create failed: ${problem.type}`);
}
```

## Quotes

[`POST /organization/{organizationId}/quote`](https://docs.etherfuse.com/v2/api-reference/quotes/create-quote) creates a new quote with a new `quoteId` on every call. Retrying is safe: a quote does nothing until you create an order from it, and it expires after 2 minutes.

## Payment accounts

[`POST /organization/{organizationId}/payment_account`](https://docs.etherfuse.com/v2/api-reference/payment-accounts/register-payment-account) returns the account that organization already holds, with `200`, when you register the same bank account or wallet again. We do not change the stored account, except that we retry a `rejected` direction with the `registrationInfo` you send, and it goes back to `pending`. If another organization holds the account, you get `409` `payment-account-unavailable`.

An [embedded wallet](https://docs.etherfuse.com/v2/guides/embedded-wallets.md) works differently: each registration creates a new wallet, even with a signer key you registered before. To retry an embedded wallet registration safely, send an `Idempotency-Key` header.

### Idempotency-Key

The header is optional for every account kind. The key is 1 to 255 printable ASCII characters, with no spaces. We keep each key per organization, so use a new key for each account you register, such as a UUID.

| Request | Response |
| - | - |
| No `Idempotency-Key` | As described above. |
| A new key | We register the account as usual and store the key. |
| Same key, same body | `200` with the account the first request returned. If you removed that account since, we add it back and return `201`. |
| Same key, different body | `409` `conflict`. Use a new key for a different account. A request that fails stores no key, so you can retry a `422` `registration-info-required` with the same key and `registrationInfo` added. |
| Two requests with the same key at the same moment | One registers the account. The other gets `409` `conflict`. Retry it with the same key to get the account. |
| A key that is empty, longer than 255 characters, or has a space or a character outside printable ASCII | `400` `invalid-request`. |

To retry an embedded wallet registration:

1. Generate the key **once** and store it with the request before the first attempt.
2. Send the same key and the same body on every retry.
3. Treat `201` and `200` as success.
4. On `409` `conflict`, the first request with the key is still running, since you sent the same body. Wait a moment and retry with the same key.

```bash theme={null}
curl -X POST https://api.sand.etherfuse.com/organization/$ORGANIZATION_ID/payment_account \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a9e-3b4d-4e5f-8a6b-7c8d9e0f1a2b" \
  -d '{
    "kind": "embeddedWallet",
    "label": "Customer wallet",
    "accountDetails": {
      "kind": "signerPublicKey",
      "chain": "stellar",
      "signerPublicKey": "02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc"
    }
  }'
```

## Webhook subscriptions

[`POST /webhook_subscription`](https://docs.etherfuse.com/v2/api-reference/webhook-subscriptions/create-webhook-subscription) allows one subscription per URL and event. A second request for the same URL and event returns `409` `conflict`. We return the signing `secret` only on the first `201`, so a retry that gets `409` does not see it. If you lost the secret, delete the subscription and create it again.

## Customer organizations: keyed on the id you send

[`POST /organization`](https://docs.etherfuse.com/v2/api-reference/organizations/create-organization) accepts an `id` you generate. If the `id` is already in use, the request returns `409` `conflict`.

1. Generate the UUID **once** and store it before the first attempt.
2. Send the same UUID on every retry.
3. Treat `409` `conflict` as "already created" and read it with [`GET /organization/{organizationId}`](https://docs.etherfuse.com/v2/api-reference/organizations/get-organization).

> [!TIP]
>
> Generate UUIDs with a standard v4 generator (`crypto.randomUUID()` in Node, a `uuid` library elsewhere).


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