Errors, retries, and payment headers
Return payment requests correctly, understand SDK errors, and retry safely.
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.
import osfrom recuut import Recuutfrom 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. |
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.