# Errors, retries, and payment headers (/guides/operate/errors-retries-headers)

## Payment headers

`PAYMENT-REQUIRED` comes with an HTTP `402` payment request. Copy it unchanged to the payer. `PAYMENT-RESPONSE` comes after payment succeeds. It is payment proof that the SDK checks before it returns the paid result.

Keep each payment-header value exactly as you received it.

`PAYMENT-RESPONSE` is encoded JSON. The SDK validates and attaches it for you. A direct HTTP integration returns the exact value from Recuut’s successful completion response. Merchants must never construct, re-encode, or edit this payment proof.

## SDK errors

| Error                      | Meaning                                | Typical response                                       |
| -------------------------- | -------------------------------------- | ------------------------------------------------------ |
| `RecuutConfigurationError` | An app setting is missing or wrong | Fix the setting. Do not retry the request. |
| `RecuutTransportError`     | The network failed before Recuut replied | Retry only when it is safe to run the paid work again. |
| `RecuutResponseError`      | Recuut returned an error response | Read `code` and `message`. |
| `RecuutUnexpectedResponseError` | Recuut replied outside the generated contract | Inspect the retained status, body, and headers; update the SDK before depending on new fields. |
| `PaymentRequiredError` | Access requires payment | Return `content` and `payment_required` to the payer unchanged. |
| `PaymentRateLimitedError` | The Merchant API rate-limited the request | Wait for `retry_after`, then retry within a bound. |

Import generic SDK errors from `recuut`. Import generated Merchant response errors and enums from `recuut.merchant_api`. For a known Merchant API error, read `code` and `message`.

### Advanced error details

Raw `content` bytes and headers remain available when you need to return a payment request exactly or inspect logs. A future status, undocumented error code, or malformed payload raises `RecuutUnexpectedResponseError` with the original status, body, and headers.

```python
import os

from recuut import Recuut
from recuut.merchant_api import (
    AccessRequest,
    AccessResource,
    PaymentRequiredError,
    SettlementAsset,
)


def relay_challenge(
    *, body: bytes, payment_required: str
) -> tuple[bytes, dict[str, str]]:
    return body, {"PAYMENT-REQUIRED": payment_required}


try:
    with Recuut() as client:
        client.payments.access(
            access_request=AccessRequest(
                revision_id=os.environ["RECUUT_REVISION_ID"],
                settlement_asset=SettlementAsset.USDC,
                resource=AccessResource(
                    url="https://merchant.example/reports/quarterly",
                    description="Quarterly report",
                    mime_type="application/json",
                ),
            )
        )
except PaymentRequiredError as error:
    if error.payment_required is not None:
        response_body, response_headers = relay_challenge(
            body=error.content,
            payment_required=error.payment_required,
        )
    else:
        print(error.status_code, error.code, error.message)
        print(error.header("Retry-After"))
```

## Retry discipline

| Response | Meaning | Safe action |
| --- | --- | --- |
| Access `402` | Payment is required | Relay the body and `PAYMENT-REQUIRED`; let the payer retry. |
| Completion `202` | Settlement is pending | Keep the result private; honor `Retry-After` and use [Get a transaction](/api-reference/transactions/payments.transactions.get/). |
| `400`, `401`, `403`, `404`, or `422` | Request, key, permission, ID, or contract error | Fix the input or configuration; do not retry unchanged. |
| `409` | Stored payment state conflicts with this request | Read the error code and transaction before deciding whether to resume. |
| `PaymentRateLimitedError` (`429`) | Rate limited | Wait for `error.retry_after`, then retry within a bound. |
| `500`, `502`, or `503` | Temporary server or payment-provider failure may exist | Retry only when protected side effects are idempotent. |

Do not retry every failure automatically. When Recuut says payment is required, return its `402` response to the payer unchanged. If `Retry-After` is present, wait that long. For a temporary network or server error, retry a limited number of times with increasing waits.

Keep the paid result private until completion succeeds. A network interruption can cause a retry. Make important side effects safe to run more than once when possible. A successful transaction and receipt are the saved payment record. An app timeout alone does not prove payment failed.

Use `payment_request_id` to match a transaction to the original attempt in your logs. Do not derive payment status from that ID.

[Inspect exact responses and schemas →](/api-reference/) · [OpenAPI JSON →](/openapi.json)
