Operate

Errors and retries

Understand SDK and Merchant API errors, and retry without repeating paid work.

Keep every payment header exactly as recuut returned it; x402 payment instructions and retry explains each one. The tables below cover everything else that can fail.

SDK errors

ErrorMeaningTypical response
RecuutConfigurationErrorAn app setting is missing or wrongFix the setting. Do not retry the request.
RecuutTransportErrorThe network failed before recuut repliedRetry only when it is safe to run the paid work again.
RecuutResponseErrorrecuut returned an error responseRead code and message.
RecuutUnexpectedResponseErrorrecuut replied outside the generated contractInspect the retained status, body, and headers; update the SDK before depending on new fields.
PaymentRequiredErrorAccess requires paymentReturn content and payment_required to the payer unchanged.
PaymentRateLimitedErrorThe Merchant API rate-limited the requestWait 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.

Relay a payment challenge
import osfrom recuut import Recuutfrom recuut.merchant_api import (    AccessRequest,    AccessResource,    PaymentRequiredError,)def relay_challenge(    *, body: bytes, payment_required: str) -> tuple[bytes, dict[str, str]]:    return body, {"PAYMENT-REQUIRED": payment_required}prompt = "Summarize the quarterly report"input_tokens = len(prompt.split())try:    with Recuut() as client:        client.payments.access(            access_request=AccessRequest(                inputs={"input_tokens": str(input_tokens)},                revision_key=os.environ["RECUUT_REVISION_KEY"],                resource=AccessResource(                    url="https://merchant.example/generate",                    description="Text generation API",                    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,        )        # Return this body and these headers to the payer with HTTP 402.    else:        print(error.status_code, error.code, error.message)

Retry discipline

ResponseMeaningSafe action
Access 402Payment is requiredRelay the body and PAYMENT-REQUIRED; let the payer retry.
Completion 202Settlement is pendingKeep the result private; honor Retry-After and use Get a transaction.
400, 401, 403, 404, or 422Request, key, permission, ID, or contract errorFix the input or configuration; do not retry unchanged.
409Stored payment state conflicts with this requestRead the error code and transaction before deciding whether to resume.
PaymentRateLimitedError (429)Rate limitedWait for error.retry_after, then retry within a bound.
500, 502, or 503recuut or the payment provider failed temporarilyRetry 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 the transaction's payment_request_id to match the original attempt in your logs.

Inspect exact responses and schemas → · OpenAPI JSON →

On this page