DOCS MENU+
REFERENCE/ERRORS
Errors
Stable codes, each paired with the action your system should take. Underlying chain messages are never forwarded.
Every error is a stable code. Most carry an action saying what your system should do, so the decision table does not have to be duplicated on your side.
TWO DELIVERY SHAPESEvery endpoint except one returns errors as an HTTP status with { error, action } in the body — the statuses in the tables below. /v1/payments/verify is the exception: it answers HTTP 200 with { valid: false, code } for every one of these outcomes, because “this payment is not proven” is an answer rather than a failure. The codes are the same; only the envelope differs.
Error shape
error response
{
"error": "INSUFFICIENT_ALLOWANCE",
"action": "CUSTOMER_ACTION_REQUIRED"
}Rate-limit and capacity errors add retry_after in seconds and set the Retry-After header. Underlying chain messages are never forwarded — they routinely echo calldata, node URLs and addresses.
Actions
SUCCESSThis period is paid. Mark it and move on.
WAITNot yet provable. Ask again with nothing changed — same hash, same subscription. Never read it as either success or failure.
RETRY_LATERNothing was spent. The identical call is safe to repeat later.
CUSTOMER_ACTION_REQUIREDOnly the customer can resolve it — balance or allowance.
STOP_SUBSCRIPTIONPermanent. Stop billing this subscription.
INVALID_REQUESTThe request is wrong. Retrying returns the same answer forever.
Payment and verification
CODEHTTPMEANING
INVALID_INTENT400Malformed, forged, or minted for another deployment.
INTENT_EXPIRED400Past its expiry, so it can no longer start a payment. Verification and refunds ignore expiry and never return it.
AMOUNT_OUT_OF_BOUNDS400Below the minimum (0.01 USDC one-time; above the fees for recurring), or above the configured ceiling.
INVALID_REFERENCE400The receipt does not contain this payment.
TERMS_MISMATCH400Recipient, amount or fee in the receipt does not match the intent.
WRONG_TOKEN400The expected USDC movements are not in the receipt.
PAYMENT_CONFIRMING409 / 200Not yet provable: either not deep enough, or no receipt seen for that hash. Re-verify the same hash; do not fulfil and do not start a second payment. Delivered inside a 200 by /v1/payments/verify, as a 409 elsewhere.
PAYMENT_ALREADY_PROCESSED409The contract has already settled this exact payment.
TRANSACTION_REVERTED400The transaction failed on chain. Nothing moved.
TRANSACTION_NOT_FOUND404No such transaction.
Subscription and charge
CODEHTTPMEANING
INVALID_SUBSCRIPTION400The capability is malformed, forged or for another deployment.
INVALID_SETUP_TOKEN400Bad setup token.
SETUP_TOKEN_EXPIRED400Setup token past its 24-hour life.
INVALID_CANCEL_TOKEN400Bad cancel token — or a bad allowance-restore token, which shares the code.
CANCEL_TOKEN_EXPIRED400Cancel and allowance-restore tokens live 15 minutes.
INVALID_SIGNATURE400The EIP-712 signature does not verify for that payer.
UNSUPPORTED_SIGNATURE_FORMAT400A signature for an account that is not deployed yet. A recurring authorization is replayed for months, so the account must exist first.
SIGNATURE_VALIDATION_TOO_EXPENSIVE400A contract wallet’s validator cost more than we will spend. Not a verdict that the signature is wrong.
PERIOD_OUT_OF_BOUNDS400Outside the configured minimum and maximum period.
NOT_DUE409The period has not opened. next_period_at says when.
ALREADY_CHARGED400This period was already collected — an idempotent success, not a failure.
PERMISSION_REVOKED400Revoked on chain, or outside the signed window. Permanent.
SUBSCRIPTION_EXPIRED409Past the authorization’s end date.
INSUFFICIENT_BALANCE400The payer’s USDC balance is short.
INSUFFICIENT_ALLOWANCE400The allowance was removed or never granted.
PERMISSION_NOT_FOUND404No such authorization on chain.
WRONG_SPENDER400The authorization names a different spender.
GAS_FEE_TOO_HIGH400The gas reimbursement exceeds what the customer signed for.
Refunds and recovery
Refund codes describe evidence rather than something P2Flux did — a refund is a transfer the merchant sends, which P2Flux only verifies.
CODEHTTPMEANING
REFUND_CONFIRMING409The refund transfer is on chain and not settled yet. The money may already have moved — verify again with the same hash; sending another refund is how a customer is paid twice. Answered 400 before 2026-08-21; branch on the code, not the status.
REFUND_AMOUNT_INVALID400Not a positive base-unit integer, or larger than the original payment.
REFUND_WRONG_MERCHANT400The refund was not sent from the wallet that received the payment.
REFUND_TRANSACTION_MISMATCH400That transaction is not the refund it claims to be.
REFUND_ORIGINAL_PAYMENT_INVALID400The settlement being refunded does not check out on chain.
INVALID_REFUND_TOKEN400Malformed, forged, or for another deployment.
REFUND_TOKEN_EXPIRED400Prepare tokens live fifteen minutes. Prepare again.
PAYMENT_NOT_FOUND200Recovery found no settlement as of that block. Not a verdict — a late one-time payment can still arrive, so ask again rather than marking the order unpaid. For /v1/charges/recover it is also the ordinary answer for a period that was skipped.
PAYMENT_RECOVERY_INCONSISTENT502The chain’s evidence contradicts itself: a settled payment with no log anywhere in its history, or a recurring settlement whose log contradicts the signed terms. Rare and abnormal; never a payment.
RECOVERY_UNAVAILABLE503Recovery is not configured on this deployment, or the bounded search could not finish on this RPC provider. Retryable; nothing to fix on your side.
Service and capacity
None of these spent anything or touched the subscription. All are safe to retry.
CODEHTTPMEANING
RATE_LIMITED429Too many requests; or, when the network fee is paid in USDC, the buyer wallet's simulations kept failing this hour, or it reached the emergency ceiling on sponsored broadcasts (cause: PAYER_EMERGENCY_LIMIT, hundreds an hour - never reached by ordinary use). Carries retry_after; nothing was spent. The hosted checkout tells the buyer to try again later, or to pay the network fee with ETH where their wallet can.
CONCURRENCY_LIMIT429Too many charges in flight at once.
RPC_BUSY503The service is saturated — not your caller specifically.
GAS_TOO_HIGH409Gas moved beyond what this subscription authorized.
GAS_QUOTE_UNAVAILABLE502Gas could not be priced at all. No transaction was attempted.
RELAYER_TX_COST_TOO_HIGH503The transaction would cost more than the relayer will risk.
RELAYER_BUDGET_EXCEEDED503The relayer’s rolling spend budget is exhausted.
RELAYER_NOT_READY503The relayer cannot safely sign right now.
RPC_ERROR502A chain read failed.
RELAYER_ERROR502Transaction submission failed.
INTERNAL_ERROR500An unexpected P2Flux failure.
INVALID_REQUEST400Schema validation failed, or the body was too large.
Paying the network fee in the payment currency has its own codes. The first four cost nothing and are decided before anything is broadcast; the last three describe an attempt that reached the chain and moved no money, because the fee and the operation it funds settle together or not at all.
CODEHTTPMEANING
PAYMENT_TOKEN_GAS_UNSUPPORTED400Not available for this network or token. The same answer every time — fall back to native gas rather than retrying.
PAYMENT_TOKEN_GAS_UNAVAILABLE503The network fee could not be priced. Transient.
PAYMENT_TOKEN_GAS_QUOTE_EXPIRED409The price the buyer accepted has lapsed. Requote and let them sign again; nobody can execute a stale quote.
PAYMENT_TOKEN_GAS_LIMIT_EXCEEDED409The quote would exceed the protocol ceiling.
INVALID_GAS_QUOTE400The quote does not belong to this payment or this operation.
INSUFFICIENT_PAYMENT_TOKEN_FOR_GAS400The wallet covers the price but not the fees on top.
SPONSORED_TRANSACTION_FAILED502Broadcast and refused on chain. Nothing moved; retry with a fresh quote.
SPONSORED_PERMIT_FAILED502The same, for an allowance change.
SPONSORSHIP_CONFIRMING409In flight. Look the settlement up; never send another.
Something more than a standard integration?
Marketplace flows, platform billing and custom settlement logic.
Discuss an integration