> ## Documentation Index
> Fetch the complete documentation index at: https://docs.perflo-api.proofof.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Handle API errors

> Parse Perflo Problem Details, preserve request IDs, and retry only when the response permits it.

Business and protected endpoints return `application/problem+json` errors based on Request for Comments (RFC) 9457. Use the stable `code`, retry flags, and response headers instead of parsing `detail` text.

## Parse the Problem Details fields

Each error response contains the same strict fields:

| Field                  | Meaning                                                    |
| ---------------------- | ---------------------------------------------------------- |
| `type`                 | Stable URL for the problem class                           |
| `title`                | Short problem title                                        |
| `status`               | HTTP status for this response                              |
| `detail`               | Human-readable detail for this occurrence                  |
| `instance`             | Request path for this occurrence                           |
| `code`                 | Stable machine-readable Perflo error code                  |
| `fields`               | Field validation failures, or `null`                       |
| `request_id`           | Correlation identifier returned in `X-Request-Id`          |
| `retryable`            | Whether retry conditions can be evaluated for this failure |
| `submission_uncertain` | Whether a provider may have accepted a financial mutation  |

The following response shows the public shape. Values vary by problem class:

```json theme={null}
{
  "type": "https://api.perflo.ai/problems/provider-reconnect-required",
  "title": "Provider reconnect required",
  "status": 409,
  "detail": "Reconnect the provider before sending this request.",
  "instance": "/v1/operations",
  "code": "provider_reconnect_required",
  "fields": null,
  "request_id": "request_12345678",
  "retryable": false,
  "submission_uncertain": false
}
```

The response never includes access tokens, proofs, cookies, provider credentials, private keys, idempotency keys, or raw request bodies.

## Interpret HTTP status classes

Use the status to classify the failure before inspecting its code:

| Status         | Meaning                                                        |
| -------------- | -------------------------------------------------------------- |
| `400`          | A required request precondition is missing or malformed        |
| `401`          | Authentication or DPoP validation failed                       |
| `403`          | A scope or access policy denied the request                    |
| `404`          | The resource does not exist for the current subject and client |
| `409`          | The request conflicts with durable state                       |
| `422`          | Strict request validation failed                               |
| `429`          | A shared request limit was exceeded (`rate_limit_exceeded`)    |
| `500`          | Perflo could not produce a valid response                      |
| `502` or `504` | An upstream read or confirmed request failed                   |
| `503`          | A required protected-request dependency is unavailable         |

Protected operations use these stable `503` codes:

| Code                              | Unavailable dependency                     |
| --------------------------------- | ------------------------------------------ |
| `authorization_keys_unavailable`  | Perflo access-token signing keys           |
| `authorization_state_unavailable` | Live authorization or security audit state |
| `rate_limit_state_unavailable`    | Shared request-rate state                  |
| `operation_state_unavailable`     | Durable operation state                    |

OAuth and OpenID Connect protocol endpoints use their protocol error grammar. They can return standard challenges instead of Problem Details.

## Decide whether to retry

Treat `submission_uncertain` as the stronger safety signal. If it is `true`, do not send the financial mutation again.

Follow these rules in order:

1. If `submission_uncertain` is `true`, read the referenced operation until its state changes.
2. If `retryable` is `false`, do not repeat the identical request. A `true` value only permits evaluating a retry after the stated condition is resolved; it never overrides `submission_uncertain` or authorizes resubmitting an uncertain mutation.
3. If the response includes `Retry-After`, wait for that interval.
4. If a DPoP challenge includes `DPoP-Nonce`, create a new proof with that nonce.
5. For a mutation whose OpenAPI operation requires `Idempotency-Key`, retry only the identical request intent with its original key. For reads or protocol requests, follow that operation’s documented retry requirements.

See [Track a financial operation](/guides/operations) for uncertain submissions and [Retry a financial request safely](/guides/idempotency) for permanent request binding.

## Preserve the request identifier

Every response includes `X-Request-Id`. The same value appears in a Problem Details body as `request_id`.

You may send `X-Request-Id` with 8 to 128 characters from `A-Z`, `a-z`, `0-9`, `.`, `_`, `~`, or `-`. Perflo replaces an invalid value with a generated identifier.
