create-quotereturns a human message inerrorplus a stable machine code inerror_code.create-pool-connectandcreate-pool-depositput the machine code inerrorand send noerror_code, except forpayment_blockedandunavailable_in_region, which carry both.payment-statusreturns a human message only, with no code at all — branch on the HTTP status there.
Status codes
Error codes
amount_below_minimum
amount_below_minimum
422. The order works out to less than 0.001 of the settlement token. Below that the contract’s fee arithmetic floors to dust, so we refuse to quote it. This floor is the same on every chain and whichever way the customer pays, and no real cart total reaches it.The response carries min_amount and token.Do: treat it as a bad cart total, not a chain problem. Limits.below_platform_minimum · below_merchant_minimum
below_platform_minimum · below_merchant_minimum
422. The order is under the minimum for that chain. below_platform_minimum is our floor; below_merchant_minimum means the minimum you configured is the binding one. Both carry min_amount and chainId.Our floor exists because settling a pay-to-address payment — the rail where the customer sends a plain transfer rather than connecting a wallet — costs a fixed amount of network gas per invoice: a one-time forwarder address is deployed and then swept into your pool. The keeper fronts that native gas and recovers it by holding back a small network fee at the forwarder — capped on-chain at 10% of what arrives, fixed into the address before the customer is ever shown it, and verified by their own browser before they pay. It comes off that one payment, not out of your settlement share. Below a certain order value that fixed gas is larger than the fee — even at its 10% cap — can carry.On the EVM test networks live today our floor is 0.001 in the settlement token — the dust floor above — because testnet gas costs us nothing. There it is not something you can trigger separately: an order small enough to hit it is caught first as amount_below_minimum, and the code you actually see is below_merchant_minimum, from a minimum you set yourself. TRON Nile is the exception, at 0.05, because TRON energy is not free even on its test network. The per-chain platform floor starts to bite when the mainnets open, where it is high on Ethereum and a couple of cents on the L2s. Roadmap.Do: quote a cheaper chain. Note this check runs at quote time, before the customer has picked how to pay, so we apply the pay-to-address floor to the whole chain — steering the customer to paying from a connected wallet does not recover a quote that was already refused. Your own minimums live in the Pool tab. Limits · Fees.pool_not_provisioned · pool_recipient_unanchored
pool_not_provisioned · pool_recipient_unanchored
409, both from the payment-creation endpoints, with the code in error.pool_not_provisioned — no pool has been created for your account, so a payment would have nowhere to settle.pool_recipient_unanchored — a pool exists, but the payout address it commits to contradicts an explicit payout address you have set on-chain for your wallet. That combination cannot arise from a healthy setup, so we refuse to serve the address. It fires only on that definitive contradiction: if you have set no explicit on-chain address, or the chain read fails, the call is not refused on this ground.Do: finish pool setup in Pool — there is no payout address to enter, you are paid to the wallet you sign in with. A customer who hits either sees “merchant hasn’t completed setup” rather than being asked for money. Verification.pool_not_deployed
pool_not_deployed
409, with the code in error. Paying from a connected wallet needs your pool to exist as code on that chain: deposit() is a contract call and reverts against an address with no contract at it. There’s no on-demand deploy on this rail — you deploy your pool yourself, from Pool, before offering the connect rail on a chain — so this code is a hard refusal, not a transient one to retry. It is a 409, not a 422 — it is a state your account is in, not a problem with the request.Do: activate the chain in Pool, or offer the pay-to-address option instead, which works before the pool is deployed.chain_not_activated
chain_not_activated
422. That chain isn’t enabled on your account.Do: enable it in Tokens, or quote a chain you have enabled. Enabling a chain there does not by itself deploy your pool on it — that is Pool, and it is why pool_not_deployed is a separate code.token_not_enabled · token_not_enabled_on_chain
token_not_enabled · token_not_enabled_on_chain
422. token_not_enabled means the token is off for your account entirely. token_not_enabled_on_chain means the token is on but not for that specific chain — enablement is per (token, chain) pair. Both are enforced server-side, so hiding the option in your UI isn’t sufficient on its own.Do: enable the pair in Tokens, or quote a different one.link_not_open · link_params_mismatch
link_not_open · link_params_mismatch
422, on a quote that carries a payment link’s order ID. link_not_open means the link is no longer open — the response echoes its status. link_params_mismatch means the amount, currency, token, or chain you quoted doesn’t match the link’s fixed terms, which are server-authoritative.Do: quote the link’s own terms, or issue a new link. Payment links.quote_expired
quote_expired
410, from the payment-creation endpoints, with the code in error. The price window lapsed before the payment was created.Do: create a fresh quote. This concerns quoting, not the address: a customer who pays an address you already issued after its quote expired is still credited — provided the payment lands inside that address’s monitoring window, 7 days by default. Past the window the address is no longer watched and the funds need manual recovery. Finality.payment_blocked
payment_blocked
403, carrying both error and error_code. A compliance check refused the payment. Deliberately non-specific, to avoid coaching evasion.Do: nothing automated. Today it fires only when payer screening is on for your account — it is off by default, you switch it on in Settings, and it can be set to monitor-only, which logs and never blocks. The platform policy can also require screening account-wide; that is not in force today. Sanctions.rate_limited
rate_limited
429, always with a Retry-After header. Two body shapes: the payment-creation endpoints return {"error":"rate_limited"}; create-quote and the status endpoints return {"error":"Too many requests","retry_after_seconds":N} with no code at all. Branch on the status, not on the body.On the payer-facing endpoints the limit is keyed by client IP, not by account.Do: back off for the duration in Retry-After. The embedded checkout does not back off for you yet: a rate-limited call shows the generic “We couldn’t start this payment.” message with a manual Try again button, with no countdown and no automatic retry. Automatic back-off is Coming. Limits.pool_mismatch
pool_mismatch
Not an API error — a terminal checkout state. The on-chain verification failed, so signing is blocked, the payment address is hidden, and the widget will not move on from it.Do: treat as serious. Check your payout settings and the
settlementAnchor in your embed. This is the protection doing its job. Verification.Three of these codes are returned by the API but the embedded checkout does not yet render a message specific to them —
token_not_enabled_on_chain, link_not_open, and link_params_mismatch currently fall back to a generic failure. Direct API callers get the codes today. Coming Specific checkout copy for all three. Roadmap.Request-shape codes
These come back fromcreate-pool-connect and create-pool-deposit in the error field. They mean the call itself was wrong, not the payment.
Handling them
Still stuck
Check the dashboard
Readiness state, recent payments, webhook deliveries.
Verify on-chain
Confirm contracts and addresses independently.