Idempotency
Required on every money-moving POST.
Every POST that moves money requires an Idempotency-Key header, and a request without one is rejected before anything happens.
Idempotency-Key: 8f0c1b4e-2b1a-4d59-9d0e-3a2f7c1b9e44Which routes require it
These seven, and they are exactly the routes that can move value:
POST /v1/warrants open
POST /v1/warrants/:id/verify check, and settle on the result
POST /v1/warrants/:id/release release in full
POST /v1/warrants/:id/partial release part
POST /v1/warrants/:id/refund refund
POST /v1/console/escalations/:id/resolve a person's decision
POST /v1/x402/pay pay a 402 with a warrant behind itRequired, not optional. Omit it and the request fails with
idempotency_key_required before any state is touched. A retry after a timeout
is the normal case, not the exotic one: the caller cannot tell a lost response
from a lost request, and without a key the safe action would be to do nothing,
which is its own kind of failure.
Generate one key per intent, not per attempt
The key identifies the thing you want to happen, so every retry of that same intent reuses it. A fresh key on each attempt is the bug this header exists to prevent: two keys means two warrants.
What a replay returns
Reusing a key with the same request returns the original outcome. Reusing it with a different body is a conflict, because the alternative is honouring a request the caller has since changed their mind about.
POST /v1/warrants HTTP/1.1
Authorization: Bearer wr_test_...
Idempotency-Key: 8f0c1b4e-2b1a-4d59-9d0e-3a2f7c1b9e44
Content-Type: application/json
{ "amount": "9900000", "payee": "someone.else", "...": "..." }409
{
"error": {
"type": "conflict",
"code": "idempotency_key_reused",
"message": "That Idempotency-Key was already used with a different request.",
"doc_url": "https://docs.gowarrant.xyz/api-reference/errors#idempotency_key_reused",
"request_id": "req_41f09c2a..."
}
}curl -X POST https://api.gowarrant.xyz/v1/warrants \
-H "Authorization: Bearer $WARRANT_API_KEY" \
-H "Idempotency-Key: 8f0c1b4e-2b1a-4d59-9d0e-3a2f7c1b9e44" \
-H "Content-Type: application/json" \
-d '{ "amount": "9900000", "payee": "someone.else" }'The four codes
| Code | Status | What happened |
|---|---|---|
idempotency_key_required | 400 | No header on a money-moving POST. |
idempotency_key_reused | 409 | Same key, different request body. |
idempotency_in_flight | 409 | The original request is still running. Retry shortly. |
idempotency_conflict | 409 | The key was claimed concurrently. Retry shortly. |
The last two are both "try again in a moment" — they are separate codes because one means your own earlier attempt is still going and the other means a race, and the distinction matters when you are reading logs at 3am.
Keys are scoped to an organisation, so two organisations can use the same key string without colliding.