Skip to main content
The PayAI facilitator applies per-client rate limits at its edge, and bounds how long a /settle request will wait for an on-chain outcome before answering. This page documents both behaviors so you can build clients that handle them correctly.

Per-IP rate limits

The facilitator’s edge (nginx ingress) enforces limits per client IP address, with a separate budget for each endpoint group so that cheap read-only calls never compete with settlement for the same allowance: Each rate has a burst allowance of 5× (short spikes above the sustained rate are absorbed; the edge rejects with HTTP 429 only once the burst allowance is exhausted). Limits may be tuned over time as capacity grows; any reduction will be announced in advance. Because verify and settle draw from separate buckets, the common verify-then-settle pattern costs one request from each — a merchant settling 40 payments per second from a single IP stays inside both budgets. These are per-source-IP limits, not account-wide: if your infrastructure sends from multiple IPs, each IP gets its own set of buckets.
If you expect sustained single-IP volume near the /settle budget, get in touch — the settlement lanes behind the edge have significantly more capacity than a single IP’s allowance, and we can set up a dedicated allowance for your address range.
Reuse connections. Keep-alive and HTTP/2 are both supported at the edge; a client that opens a fresh TLS connection per request pays an extra round trip on every call and consumes its concurrent-connection budget far faster than a pooled client.

Telling responses apart: 402 vs 429 vs 5xx

The status code alone tells you how to react: Retrying is safe in both non-402 cases even for /settle: verification is read-only, and settlement is idempotent — re-submitting an identical payload returns the recorded outcome of the original attempt and can never cause a second on-chain payment. Unsuccessful settlements that the facilitator did process return HTTP 200 with success: false and a machine-readable errorReason (for example transaction_failed, duplicate_settlement, or settlement_pending) — a JSON x402 body always means the facilitator handled the request.

The settlement_pending response

POST /settle waits for the on-chain outcome, but only up to a bounded response budget (currently 100 seconds). If the settlement is still in flight when the budget expires — for example during network congestion — the facilitator answers instead of letting the connection time out. This applies to every supported network — Solana and all EVM chains alike. The response shape and the reconciliation procedure are identical everywhere; the only per-chain difference is whether the transaction field can be populated, covered below.
Key properties of this response:
  • It arrives with HTTP 200, like every other unsuccessful settlement, so standard x402 clients surface it in the normal !success path rather than as a thrown transport error.
  • It is not a verdict. The settlement job keeps running after the response is sent — the payment may still land on-chain. Do not treat settlement_pending as a failure, and do not treat it as a success.

How to reconcile: re-submit the identical payload

To learn the real outcome, re-POST the exact same payload (the same payment payload and payment requirements) to /settle:
  1. While the original attempt is still in flight, you receive 409 duplicate_settlement — keep polling with backoff.
  2. Once the attempt resolves, the facilitator serves the recorded outcome: the cached success response (including the transaction hash) if the payment landed, or the recorded failure if it did not.
Replaying the identical payload is always safe: settlement is idempotent, and a replay can never cause a second on-chain payment.

Solana vs EVM: whether transaction is populated

The transaction field of a settlement_pending response differs by chain family:
  • Solana — transaction is always empty. Send and confirmation happen inside a single settlement job, so no signature is available to the API before the outcome is known. Re-submitting the payload is the only reconciliation path on Solana — there is no hash to look up on-chain.
  • EVM (Base, Polygon, Arbitrum, Avalanche, Sei) — if the transaction was already broadcast when the budget expired, transaction carries the broadcast hash. You can watch that hash on-chain directly, in addition to (or instead of) polling /settle.
On EVM, a populated transaction means the transaction was broadcast, not that it landed. Wait for confirmation on-chain, or poll /settle for the recorded outcome.

Client-side timeouts

Some x402 client libraries default to shorter timeouts than the facilitator’s response budget (for example, 30 seconds in @x402/core’s HTTPFacilitatorClient). A client-side timeout behaves like an edge timeout: you get no response body, but the settlement is still in flight. The reconciliation procedure is the same — re-submit the identical payload to retrieve the recorded outcome.

Need help?

Join our Community

Have questions or want to connect with other developers? Join our Discord server.