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-Key makes a single HTTP call safely retriable. external_uid tags 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-9a1b2c3d4e5f

Generate 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 conflict with error code conflict and a message indicating idempotency key reuse with a mismatched request. This is a safety net against accidentally reusing a UUID for an unrelated operation.
Idempotency-Key cache behavior

Status codes

OutcomeStatusError code
First execution succeedsendpoint's normal success status
Replay, same bodycached status (same as first call)
Replay, different body409conflict

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.