> ## Documentation Index
> Fetch the complete documentation index at: https://www.pickfu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> How to retry POST /v2/orders and POST /v2/surveys/{id}/publish safely with the Idempotency-Key header.

The two v2 endpoints that move money **require** an `Idempotency-Key` header:

* `POST /v2/orders` — buy a credit pack.
* `POST /v2/surveys/{id}/publish` — pay for and publish a draft survey.

`GET`, `PATCH`, and `DELETE` calls are naturally idempotent and safe to retry on network error. `POST /v2/surveys/{id}/stop` is also safe to retry — stopping an already-stopped survey is a no-op.

Other `POST` calls (`POST /v2/surveys`, `POST /v2/media`, `POST /v2/webhook-endpoints`) do **not** accept `Idempotency-Key` and are not deduplicated server-side. A retry after a lost response can create a duplicate draft survey, upload the same media twice, or register a second webhook endpoint. If you can't tell whether the original attempt landed, list the resource (`GET /v2/surveys?...`, `GET /v2/webhook-endpoints`) or use your own request identifier (`externalId` on surveys) to reconcile before retrying.

## The key

Send a unique string per attempt in the header:

```bash theme={null}
Idempotency-Key: 5f8b1c3e-4a7d-4a4c-9b0f-3f7c1a5b2c9d
```

* Up to **255 characters**. A UUID or a hash of your own request identifiers both work.
* Scoped **per team**. Two different teams can reuse the same key without collision.
* Retained for **24 hours** after the first attempt.
* Generate a fresh key **per attempt**. Reusing a key is only correct when the request body is byte-identical and you want the same server response.

## Replay behaviour

Retrying with the **same key and the same body** replays the original response:

* If the original succeeded (2xx), you get the exact same 2xx body back, with `Idempotent-Replayed: true` in the response headers.
* If the original returned an error, only the first response is stored — non-2xx responses are **not** replayed. Retrying is a fresh attempt.

Retrying with the **same key but a different body** fails with `409 idempotency_mismatch`. Either use a new key or send the identical body.

Retrying while the original attempt is still running (concurrent duplicates) fails with `409 idempotency_in_flight` and a `Retry-After` header — wait, then retry.

## 402 on publish never binds the key

A `402` from `POST /v2/surveys/{id}/publish` (`insufficient_credits`, `card_declined`, `card_authentication_required`) does **not** bind the Idempotency-Key. The survey stays as a draft, nothing was charged, and you can publish again — but you must use a **new** Idempotency-Key on the retry. The old one is unreserved.

## Credit-pack order retry guarantee

Once you start a credit-pack checkout with `POST /v2/orders`, its identity and parameters are frozen for 24 hours on the same Idempotency-Key. If a network hiccup drops the response, retry with the same key and body to resume the same order and Stripe Checkout Session — you'll get back the same `checkoutUrl` and `orderId` and won't double-charge the buyer.

Two things end that guarantee:

* The 24-hour window expires.
* You send a **different** body under the same key (which fails `409 idempotency_mismatch` before charging anything).

For a genuinely different purchase, always generate a new Idempotency-Key.

## Example

```ts theme={null}
const idempotencyKey = crypto.randomUUID();

async function attemptPublish() {
  return fetch(`https://api.pickfu.com/v2/surveys/${surveyId}/publish`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Idempotency-Key": idempotencyKey,
    },
  });
}

// Safe to retry the exact same call on network error — the server will
// either replay the original 2xx or continue the in-flight attempt.
let res = await attemptPublish();
if (res.status === 409) {
  const { error } = await res.json();
  if (error.code === "idempotency_in_flight") {
    await new Promise((r) => setTimeout(r, 2000));
    res = await attemptPublish();
  }
}
```


## Related topics

- [PickFu API v2](/docs/api-reference/v2/introduction.md)
- [Errors](/docs/api-reference/v2/errors.md)
- [Publishing and payments](/docs/api-reference/v2/payments.md)
- [PickFu REST API reference and conventions](/docs/api-reference/introduction.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.