Skip to documentationDocumentation
On this page

Idempotency

Retry mutations safely with permanent keys and the same canonical request.

Every merchant POST requires an Idempotency-Key header. Create and persist a key before sending the request; reuse it when the outcome is unknown.

Usage#

curl
curl --request POST 'https://api.thehalfin.com/v1/invoices' \
  --header "X-API-Key: $HALFIN_API_KEY" \
  --header 'Idempotency-Key: order-1001-invoice' \
  --header 'Content-Type: application/json' \
  --data '{
    "amount": "25.00",
    "currency": "USD",
    "external_id": "order-1001"
  }'

The header contains 1–255 printable ASCII characters without spaces. UUIDs are a convenient choice, but UUID format is not required. Supply one unquoted key value; do not put it in the JSON body.

Scope and lifetime#

Keys are scoped to the merchant and environment, shared by all API keys in that scope. Use a distinct key for each logical mutation, including create, confirm, and cancel. Reusing a key on another operation or resource conflicts.

Once an outcome is stored, the key does not expire and cannot be recycled. An exact retry returns the original business outcome and Idempotent-Replayed: true. It does not create another resource or move funds again. A replayed creation response may show an earlier status; retrieve the resource to read its current state.

What counts as the same request#

The fingerprint covers the operation, path parameters, query, and entire canonical JSON body. JSON object-key ordering and insignificant whitespace do not matter. Array order and field values do matter, including annotations such as description, metadata, and auto_confirm.

Retry changeResult under the same key
JSON formatting or object-key orderSame request
"10" changed to "10.00"409 conflict
A currency's letter case changed409 conflict, even when the value normalizes to the same currency
Metadata or description changed409 conflict

An identical request still executing returns 409 in_flight. Wait and retry the original request with the same key.

Released keys#

Idempotency-Key-Released: true means the server confirmed that this attempt left no accepted outcome bound to the key. After correcting the refused request, you may reuse that key. A concurrent request can still claim it.

Do not infer release from a 400, 404, 409, or 422 status alone. Not every refusal releases a reservation. If the header is absent and the outcome is uncertain, preserve the original request and key; do not try a new key to bypass the uncertainty.

Retry strategy#

OutcomeAction
Success or replaySave the returned resource ID; use GET for its current state
Network timeout, connection loss, 500, or 503Back off with jitter and retry the same key and body
409 in_flightRetry the same request after a delay
409 conflictStop; compare the persisted original request with the new request
Confirmed releaseCorrect the refusal and reuse the key if appropriate
401 / 403Fix authentication or permissions; do not spin retries

Respect Retry-After when supplied. Keep retry attempts bounded and escalate an unresolved result for reconciliation. Webhook deduplication uses the event id; it is independent of request idempotency keys.