Skip to main content

Guarantee that each POST request executes exactly once.

What is an Idempotency Key?

When you POST a request (create a transfer, etc.) you include a unique Idempotency-Key header. Brale stores that key along with the request signature so if you retry—because the network dropped or timed out—Brale recognizes the duplicate and returns the original response instead of performing the operation twice. The primary goal is to prevent double spends. Without idempotency keys, a retry after a network failure could result in two transfers being created and two debits hitting your customer’s account. Keys are required on all POST requests and should be reused when retrying the exact same operation. Transient failures — like a server error, a timeout, or an insufficient_balance that the customer has since resolved — can all be retried with the same key. Generate a new key only when the request itself is intentionally changing (e.g. fixing a validation error or initiating a second distinct transfer).
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000

When is it required?

Endpoint typeIdempotency-Key header
POST /… (create)Required
GET /…, PUT/PATCH /…, DELETE /…Do NOT include
If the header is missing on a POST request, Brale returns 400 Bad Request.

Example

curl --request POST \
  --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \
  --header "Authorization: Bearer ${AUTH_TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
    "amount": {"value": "10", "currency": "USD"},
    "source": {"value_type": "usd", "transfer_type": "wire"},
    "destination": {
      "address_id": "'"${ADDRESS_ID}"'",
      "value_type": "sbc",
      "transfer_type": "base"
    }
  }'
Most HTTP clients let you generate a UUID at call time or set a default header.

Request Responses

ScenarioStatusBody
First successful call201: CreatedNormal JSON response
Retry with same body201: CreatedSame JSON as first call
Retry with same key but different body422: Unprocessable Entity{ "This Idempotency-Key can't be reused with a different payload or URI"" }

When to reuse the same key vs. generate a new one

Reuse the same key when retrying the exact same operation:
ScenarioAction
Network timeout — no response receivedRetry with same key
5xx server errorRetry with same key (with backoff)
429 rate limitedRetry with same key (with backoff)
422 insufficient_balance — customer has since funded their accountRetry with same key
Generate a new key when the request itself is changing:
ScenarioAction
Fixing a validation error before resubmittingNew key
Intentionally initiating the same operation a second timeNew key
422 idempotency_key_mismatchNew key — and fix the request body

Insufficient balance

422 insufficient_balance is a special case. The request is valid — the account simply didn’t have enough funds at the time. Once the customer funds their account, they can retry with the same idempotency key and the transfer will be processed. This is different from permanent validation failures (e.g. invalid_account_number), which will always fail regardless of account state. For those, the request itself must change, which requires a new key.

Troubleshooting

ErrorLikely causeFix
400: Missing Idempotency-KeyHeader omitted on POSTAdd a unique key (UUID).
422: Unprocessable EntityRe-used key with different request bodyGenerate a fresh key per logical action.

Best Practices

  • Generate a UUID per logical action. Store it with your job record so retries use the same value.
  • Do not share keys across different endpoints. Keep one key scoped to one operation.
  • On insufficient_balance, do not generate a new key — store the original key and reuse it after the account is funded.