Idempotency

Network failures, timeouts, and process crashes are unavoidable. Aeses supports idempotency keys so that retrying a POST is safe — the same logical request only takes effect once, no matter how many times you retry.

Sending an idempotency key

Add the Idempotency-Key header to any POST request. The value can be any string up to 255 characters; a UUID v4 is a good default.

Idempotent request
curl https://api.aeses.io/v1/deposits \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
  "asset": "USDT",
  "chain": "ethereum"
}'

The first response is stored for 24 hours. Subsequent requests with the same key and the same body return the original response — including the original status code — without performing the operation again.

Where to use it

We strongly recommend sending an idempotency key on every POST. It is required in practice for:

  • POST /v1/deposits — avoids generating multiple addresses on retry.
  • POST /v1/withdrawals — prevents double-spending if your client retries.
  • POST /v1/charges — prevents creating duplicate payment intents.
  • POST /v1/webhook-deliveries/:id/replay — re-delivery is naturally idempotent, but a key prevents accidental floods.

GET and DELETE are naturally idempotent and do not require a key.

Generating keys

Generate a key once per logical operation, before the first attempt. Persist it alongside the operation in your database, and reuse it on every retry until the operation succeeds. A common pattern:

operation_id = uuid4()
save(state="pending", idempotency_key=operation_id)

while not done:
  try:
    response = api.create_deposit(..., idempotency_key=operation_id)
    save(state="confirmed", deposit_id=response.id)
    done = True
  except RetryableError:
    sleep(backoff())

Never generate a fresh key inside the retry loop — that defeats the purpose.

Replay behavior

The replayed response is byte-identical to the original, including any error response. If the original request returned 400 invalid_request_error, the same 400 is replayed; no business state changes.

The original Request-Id is preserved on replays for auditability. The HTTP response additionally includes:

  • Idempotency-Replayed: true — present when the response is served from cache.

Conflicts

If you reuse the same key with a different request body, Aeses returns 409 idempotency_key_reused:

Conflict response
{
"error": {
  "type": "idempotency_error",
  "code": "idempotency_key_reused",
  "message": "Idempotency-Key already used with a different request payload.",
  "request_id": "req_01HXYZ..."
}
}

This usually indicates a bug in your client: the key was reused for an unrelated operation. Generate a fresh key for each new logical operation.

TTL

Idempotency records expire 24 hours after the original request. After that, the same key can be reused for a new operation without conflict. If you retry beyond the 24-hour window, the request is processed as if it were new — design retries to complete well within this window.