Skip to main content
Idempotency lets you retry a request without risk of performing the same action twice. This matters most for two kinds of requests: those that create a resource, such as saving an external account, and those that move money, such as an ACH transfer. If a network error interrupts either one, you can’t always tell whether the server received it. Retrying with an idempotency key guarantees the action runs at most once.

Using an idempotency key

An Idempotency-Key header is mandatory on every POST request. A request without one is rejected with an error. A key must be between 10 and 256 characters long (inclusive) and may contain only letters, numbers, dashes (-), underscores (_), and colons (:). A request whose key is too short, too long, or includes any other character is rejected with an error. Choose a key that stays the same across retries of the same action. The best key is a meaningful identifier from your own database, such as the ID of the payout or invoice the transfer settles. Reusing that identifier when you retry is what lets us recognize the retry and return the original result. You can pass a random version 4 UUID instead, but a new random key on each attempt provides no idempotency at all, because there is nothing for us to match against.
curl https://api.getlemma.com/v0/ach-transfer \
  -H "Authorization: Bearer your_api_key" \
  -H "Idempotency-Key: payout_8f21c3a9" \
  -H "Content-Type: application/json" \
  -d '{
    "source_account_id": "account_Rv4nBt8xKw2pMh6s",
    "destination_external_account_id": "external_account_Bx5nTm8hKw3pVd7c",
    "amount": 150000
  }'

How it works

The first request made with a given key is processed normally, and we store the key against the resource it created. Any later request that reuses the same key returns that resource without running the action again. A replayed request returns the latest state of the resource, not a snapshot of the original response. If the resource has changed since the first call (for example, a transfer that has moved from pending to completed), the replay reflects that current state rather than what was returned the first time. A response served this way includes an Idempotency-Replayed: true header so you can tell it apart from a freshly processed request. Keys are stored permanently, so a replay works no matter how much time has passed.

Retries

The reason to send an idempotency key is to retry safely when you can’t tell whether we received the first request — a dropped connection or a timeout. When the outcome is unknown, retry with the same key. We resolve it one of three ways:
  • The first request succeeded: we return the modified object’s latest state and set Idempotency-Replayed: true in the header.
  • The first request is still in progress: you get 409 Conflict. Wait a moment and retry the same key.
  • The first request failed: you get 500, with a message that something went wrong on the previous request for this key.
A request you already know failed — you received an error response — should not be retried with the same key. Because we can’t be certain a failed request had no effect, we never re-run it: the key is spent and keeps returning 500. Use a new idempotency key to make a fresh attempt.

One action per key

A key is bound to the endpoint it was first used on. Reusing it on a different endpoint returns 422 Unprocessable Entity. Within the same endpoint we do not compare request bodies, so reusing a key returns the original resource even if you send a different body. Always use a distinct key for each distinct action so a changed request is never mistaken for a retry.

Idempotency keys are mandatory

Every POST request requires an Idempotency-Key, and a request sent without one is rejected with an error. The endpoints where this protects you from costly duplicates include: GET requests are already idempotent and do not take the header.