> ## 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.

# Errors

> Every v2 non-2xx response uses the same Stripe-shaped { error, requestId } envelope. What each field means and how to react to each status code.

Every non-2xx response from v2 — including validation failures, `404` for unknown routes, and rate-limit rejections — uses the same envelope:

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "questions is required",
    "param": "questions"
  },
  "requestId": "req_01J9C2VZH8..."
}
```

`requestId` also appears in the `X-Request-Id` response header. Log it and quote it in support tickets.

## Fields

<ResponseField name="error.type" type="string" required>
  The error family. One of `invalid_request_error`, `authentication_error`, `permission_error`, `rate_limit_error`, `card_error`, or `api_error`.
</ResponseField>

<ResponseField name="error.code" type="string" required>
  A stable machine-readable code. Branch on `code`, not `message`. See the table below.
</ResponseField>

<ResponseField name="error.message" type="string" required>
  Human-readable explanation. Not stable — do not parse it.
</ResponseField>

<ResponseField name="error.param" type="string">
  The specific request field the error refers to, when applicable. Omitted otherwise.
</ResponseField>

<ResponseField name="requestId" type="string" required>
  Server request ID, equal to the `X-Request-Id` header.
</ResponseField>

Some codes carry extra detail alongside `type`/`code`/`message`. `card_error` responses on publish include `shortfall`, `suggestedPack`, `billingPortalUrl`, and `declineCode`; `quote_changed` includes `quotedCost` and `currentCost`. See [Publishing and payments](/docs/api-reference/v2/payments).

## Status codes and common codes

| Status | Type | Common codes | What it means |
| - | - | - | - |
| `400` | `invalid_request_error` | `parameter_missing`, `parameter_invalid`, `parameter_unknown`, `idempotency_key_required` | The request body or query is malformed — or `Idempotency-Key` is absent on publish/order endpoints. Fix the request; don't retry. |
| `401` | `authentication_error` | `authentication_required` | Missing, malformed, or unknown Bearer token. |
| `403` | `permission_error` | `api_key_unscoped`, `insufficient_scope`, `team_mismatch` | Key exists but isn't allowed to do this. `api_key_unscoped` = key has no scopes; `insufficient_scope` = wrong scope. |
| `404` | `invalid_request_error` | `resource_missing` | Unknown route or a resource that doesn't belong to this team (v2 does not distinguish "not yours" from "not there"). |
| `409` | `invalid_request_error` | `quote_changed`, `quote_required`, `survey_already_published`, `idempotency_mismatch`, `idempotency_in_flight` | The request conflicts with the current state. See [Idempotency](/docs/api-reference/v2/idempotency) and [Payments](/docs/api-reference/v2/payments). |
| `402` | `card_error` | `insufficient_credits`, `card_declined`, `card_authentication_required` | Publish couldn't fund the survey. Nothing was charged. See [Payments](/docs/api-reference/v2/payments). |
| `422` | `invalid_request_error` | `request_unprocessable` | Semantically invalid (e.g. targeting trait unavailable in this country). |
| `429` | `rate_limit_error` | `rate_limit_exceeded` | Per-team rate limit exceeded. See [Rate limits](/docs/api-reference/v2/rate-limits) and back off using `Retry-After`. |
| `500` | `api_error` | `internal_error` | Server bug. Safe to retry with exponential backoff. |
| `502` | `api_error` | `upstream_unavailable` | A dependency (Stripe, CSV exporter, media host) was unavailable. Retry with backoff. |

## Handling in code

Branch on `error.code`, not `error.message` or the HTTP status alone. Codes are stable across releases; messages and status codes may be tightened over time.

```ts theme={null}
const res = await fetch("https://api.pickfu.com/v2/surveys/srv_.../publish", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Idempotency-Key": crypto.randomUUID(),
  },
});

if (!res.ok) {
  const { error, requestId } = await res.json();
  console.error({ status: res.status, code: error.code, requestId });

  if (error.code === "quote_changed") {
    // Refresh the survey, show the new quote, publish again with a new key.
  } else if (error.code === "insufficient_credits") {
    // Send the buyer to error.suggestedPack; publish again after order.paid.
  } else if (error.code === "card_declined") {
    // Send the buyer to error.billingPortalUrl to update the card.
  }
}
```

The 402 codes (`insufficient_credits`, `card_declined`, `card_authentication_required`) never bind the Idempotency-Key — retry publish with a **new** key after funding.


## Related topics

- [PickFu REST API reference and conventions](/docs/api-reference/introduction.md)
- [PickFu CLI for surveys and automation](/docs/integrations/cli.md)
- [PickFu API v2](/docs/api-reference/v2/introduction.md)
- [Why was my card declined at checkout?](/help/why-was-my-card-declined-at-checkout.md)
- [How to build PickFu surveys with URL parameters](/help/how-to-build-pickfu-surveys-with-url-parameters.md)


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