Skip to main content
Errors return a JSON body. The shape is not uniform across the API yet, so read both fields. Match on the code, not the message — messages may be reworded.
  • create-quote returns a human message in error plus a stable machine code in error_code.
  • create-pool-connect and create-pool-deposit put the machine code in error and send no error_code, except for payment_blocked and unavailable_in_region, which carry both.
  • payment-status returns a human message only, with no code at all — branch on the HTTP status there.
One line covers all three shapes:

Status codes

Error codes

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.
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.
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.
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.
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.
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.
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.
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.
403, carrying both fields. The request came from a location where we don’t offer the service. It runs before anything else on the payer path and cannot be switched off per account.Do: nothing. No jurisdiction is named in the response, deliberately. What we don’t do.
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.
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 from create-pool-connect and create-pool-deposit in the error field. They mean the call itself was wrong, not the payment.

Handling them

Distinguish customer-recoverable errors — try another chain, wait a moment — from merchant configuration errors, which the customer can do nothing about. The second kind should page you, not confuse them.

Still stuck

Check the dashboard

Readiness state, recent payments, webhook deliveries.

Verify on-chain

Confirm contracts and addresses independently.