Idempotency
Idempotency
Every write endpoint on the API honors the Idempotency-Key header. Use it on every write request — without it, a retried POST that already succeeded on the server will execute again and double-charge fees, double-mint NFTs, or rebroadcast a transaction you've already signed.
Idempotency-Key vs. external_uid
Idempotency-Keymakes a single HTTP call safely retriable.external_uidtags the underlying business object so you can reconcile API resources with your own ledger. Use both — different scopes, different lifetimes.
The header
Idempotency-Key: 5f3e2c8a-1d1d-4f9b-a2c2-9a1b2c3d4e5fGenerate a fresh v4 UUID for each logical operation — not per HTTP call. A retry of the same operation must reuse the same key; a new operation must use a new key.
Cache semantics
The API stores (api_key, sha256(request_body)) → response in the Supabase idempotency_keys table. The lookup key is the pair of the API key and the body hash, so:
- First call — request runs end-to-end and the response (status code + body) is persisted under the key.
- Replay with the same key and same body — the server returns the cached response without re-executing. Same status, same body, byte-identical.
- Replay with the same key but a different body — the server returns
409 conflictwith error codeconflictand a message indicating idempotency key reuse with a mismatched request. This is a safety net against accidentally reusing a UUID for an unrelated operation.
Status codes
| Outcome | Status | Error code |
|---|---|---|
| First execution succeeds | endpoint's normal success status | — |
| Replay, same body | cached status (same as first call) | — |
| Replay, different body | 409 | conflict |
Note: the conflict code is shared with other 409 responses in the API. There is no separate idempotency_conflict code.
When to use it
Always, on every write call. In particular:
- Pool create — a duplicate would launch two pools.
- Fees claim — a duplicate would attempt a second claim against the same accrued balance and fail mid-flight, wasting compute.
- Airdrop init — a duplicate would attempt to write a new on-chain config PDA from the same seeds and fail.
- Token create — a duplicate would mint a second token if the mint keypair differs across retries.
POST /v2/transactions— even raw submits should be idempotent so an HTTP-level retry doesn't try to re-broadcast a transaction the server already accepted.
Read endpoints (GET) ignore the header — they're naturally idempotent.
Recommended client pattern
import { randomUUID } from "crypto";
const idempotencyKey = randomUUID();
async function claim() {
return fetch("https://api.piratecrew.fun/v2/pools/POOL_ADDRESS/fee-claims", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.PIRATE_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({ kind: "dbc", payer: "WALLET_PUBKEY", mode: "unsigned" }),
});
}
// Safe to retry — same key, same body returns the cached response.
await retryWithBackoff(claim);Generate the key outside the retry loop. If you regenerate the UUID on every attempt you defeat the entire mechanism.