Idempotency
Mutating endpoints (anything that creates a resource, charges credits, or starts a job) accept an Idempotency-Key header. Use it on every non-idempotent request you might retry.
When to use it
Section titled “When to use it”- Submitting
POST /v1/codeor any other coding/codebook write. A retry without an idempotency key can re-charge credits. - Creating projects, questions, or webhooks.
- Anywhere your network layer auto-retries (e.g. a fetch wrapper with exponential backoff).
You don’t need an idempotency key on GET requests or on DELETE requests — they’re naturally idempotent on the server.
How it works
Section titled “How it works”curl -X POST https://api.surveycoder.io/v1/code \ -H "x-api-key: $SCP_API_KEY" \ -H "Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" \ -H "Content-Type: application/json" \ -d '{ "question": "...", "responses": ["..."] }'import { randomUUID } from 'node:crypto';
const key = randomUUID();
await sc.code( { question: '...', responses: [/* ... */] }, { idempotencyKey: key });import uuid
key = str(uuid.uuid4())sc.code(question="...", responses=[...], idempotency_key=key)The server caches the response for 24 hours, keyed on (api_key, idempotency_key). Replaying the exact same request returns the cached response — same status code, same body, same request_id.
- Keys must be unique per logical request. A UUID v4 is the safest default.
- Keys are scoped per API key, not per org. Two keys can use the same idempotency key without conflict.
- The request body must match the original. If you reuse a key with a different body, the API returns
VALIDATION_ERRORwithmessage: "Idempotency-Key reused with different body". - Keys expire after 24 hours. A 25-hour-later retry creates a new request.
Recommended pattern
Section titled “Recommended pattern”Pair idempotency keys with bounded retries. We recommend at most 3 attempts on 5xx responses (or 429 with a Retry-After header):
async function codeWithRetry(payload: CodeRequest) { const idempotencyKey = crypto.randomUUID(); let lastErr: unknown; for (let attempt = 0; attempt < 3; attempt++) { try { return await sc.code(payload, { idempotencyKey }); } catch (err) { lastErr = err; if (!isRetryable(err)) throw err; await sleep(2 ** attempt * 1000); // 1s, 2s, 4s } } throw lastErr;}isRetryable should return true for HTTP 429, 502, 503, 504, and network errors; false for 4xx errors that aren’t 429.
Related
Section titled “Related”RATE_LIMITED— when to retry vs back offSERVICE_UNAVAILABLE— always safe to retry with an idempotency key