# Errors and retries

Interpret problem details and recover safely from validation, conflict, and rate-limit responses.

- Source URL: https://www.signcustomiser.com/help/api/guides/errors-and-retries/
- Markdown URL: https://www.signcustomiser.com/help/api/guides/errors-and-retries.md

## Guide

API v3 returns every non-2xx response as an RFC 9457 problem document with the media type `application/problem+json`. Branch on `status` and `code`, not on title or prose.

## Problem shape

```json
{
  "type": "https://www.signcustomiser.com/help/api/problems/invalid-pricing-model",
  "title": "Invalid pricing model",
  "status": 422,
  "code": "invalid_pricing_model",
  "detail": "frame_fit customisers cannot use letter price lists.",
  "errors": [
    {
      "pointer": "/pricing_model",
      "code": "unsupported_value",
      "allowed_values": ["simple_letter", "advanced_letter", "frame_fit"]
    }
  ],
  "request_id": "req_..."
}
```

Every problem has `type`, `title`, `status`, `code`, `detail`, and `request_id`.

- `type` links to documentation for the problem class.
- `code` is the stable machine identifier. Its meaning does not change within v3.
- `detail` describes this occurrence and can change. Do not parse it.
- `request_id` is safe to record and is the value to include when asking for support.
- `errors` appears when the server has field-level details. A body field uses a JSON Pointer such as `/pricing_model`; a query error may identify a `parameter` instead. Enum errors include `allowed_values`.

Do not assume `errors` is present. Authentication, authorisation, rate-limit, conflict, and server problems often have enough information at the top level.

## Decide by status and code

| Status | Meaning | Recovery |
|---|---|---|
| `400` | The request could not be parsed. | Fix its syntax or content type. Do not retry it unchanged. |
| `401` | The credential is missing, invalid, expired, or revoked. | Replace the key or bearer header. |
| `403` | The key is valid but lacks access. | For `insufficient_scope`, compare `required_scopes` with `granted_scopes`. |
| `404` | The resource is unknown or does not belong to this Store. | Refresh the Store-scoped resource list; do not use the response to infer another Store's data. |
| `409` | The request conflicts with current state. | Resolve the named conflict. Idempotency conflicts have the special rules below. |
| `422` | The request is valid JSON but violates the contract. | Correct the fields named by `errors`; use `allowed_values` rather than guessing. |
| `429` | The key has reached a rate or workload limit. | Wait for `Retry-After`, then retry as described below. |
| `5xx` | The server failed to complete the request. | Retry with backoff. Preserve the same idempotency key and body for writes. |

## Retry reads

Retry a `GET` after a network failure, `429`, or `5xx`. Use capped exponential backoff with jitter. When a `429` includes a `Retry-After` header, wait at least that many seconds; the problem body also carries `retry_after` for clients that cannot inspect headers.

Do not automatically retry `400`, `401`, `403`, `404`, or `422` unchanged. Those responses need a credential, resource, or request correction first.

## Retry writes with idempotency

Every v3 write with side effects accepts an `Idempotency-Key` header. Generate a unique value for one logical operation and keep it with that operation for at least 24 hours.

```http
Idempotency-Key: order-018f2f16-7d52-7a66-bf71-0b90b9d6fce1
```

If a write times out, loses its connection, returns `429`, or returns `5xx`, retry with the **same key and byte-equivalent body**. A completed operation replays its original response and includes `Idempotency-Replay: true`, so a transport failure does not create the resource twice.

Two conflict codes need different handling:

- `idempotency_key_in_flight`: the first request is still running. Wait for its `retry_after`, then send the same key and body again.
- `idempotency_key_conflict`: the key was already used with a different body. Do not overwrite its meaning. Correct the request, generate a new key, and submit it as a new logical operation.

A `422` validation response means the operation was not accepted. Correct the body and use a new idempotency key. The pricing validation endpoint is side-effect free and does not use idempotency.

## Logging without leaking data

Record the HTTP method, path template, status, stable `code`, `request_id`, attempt number, and idempotency key identifier. Do not log API keys, bearer headers, or full request bodies. Redact customer text and personal data before attaching a problem response to a ticket.
