Errors
HTTP status codes from Penni's API, and the error codes the Node.js middleware reports.
Penni's own checks answer with an HTTP status and { "error": "…" }. Results from the facilitator itself (isValid, success and their reasons) pass through unchanged.
API status codes
| Status | Meaning |
|---|---|
400 | The body is not valid JSON, or paymentRequirements is missing. |
401 | Missing or invalid API key, or a key for another site. |
402 | Out of credits. No settlement was attempted. Never for test payments. |
403 | Site paused, payTo is not the site's wallet, or the token is not accepted for this site. |
409 | This Solana transaction was already settled; a payment counts once. |
429 | Daily limit of free test settlements reached. Resets at 00:00 UTC. |
502, 503 | The facilitator could not be reached, or doesn't serve that chain. Safe to retry verify; for settle, check the transaction first. |
Common payment reasons
| Reason | What happened |
|---|---|
insufficient_funds | The payer doesn't hold enough of the token. |
invalid_payment_requirements | The payment was signed for another price, token, wallet or network, for example after a price change. |
settlement_pending | Cardano only: confirmations aren't in yet. Send the same settle body again. |
Node.js middleware codes
The middleware reports every problem to onError as a PenniError with a code:
| Code | Meaning | Response |
|---|---|---|
invalid_payment | The payment header isn't a payment | 402 |
requirements_mismatch | Signed for another price, token, wallet or network | 402 with the current price |
verify_failed | The facilitator refused the payment | 402 |
settle_failed | Settlement failed on-chain | 402 |
gateway_rejected | Penni refused for a reason on your side (key, paused site, credits, test limit) | onUnavailable |
config_rejected | Wrong site ID or API key | onUnavailable |
config_unavailable, facilitator_unavailable | Penni unreachable, timed out or 5xx | onUnavailable |
not_ready | Priced path without payment options, or blocked while out of credits | whenNotReady or the dashboard setting |
hook_failed | Your onPaid threw; the payment still succeeded | none |