Furlpay Docs
Open App

Architecture

Error Codes

Errors are boring on purpose: a machine-readable code, an optional human detail, and the right HTTP status. Nothing sensitive ever rides an error body.

Response shapes

Product APIs return a flat error object; ops APIs use the envelope with a request id:

jsonproduct API error
{ "error": "amount_over_limit", "detail": "amount exceeds the per-transaction limit ($500)" }
jsonops API error
{
  "ok": false,
  "error": { "code": "reason_required", "message": "A reason (min 8 chars) is required." },
  "meta": { "requestId": "req_…", "timestamp": "2026-07-12T00:00:00Z" }
}

x402 endpoints are special: a rejected payment returns 402 with an error string and a fresh accepts quote, so an agent can recover in one round-trip.

HTTP status conventions

  • 400 — validation failed (zod), malformed payload, bad parameters.
  • 401 — no valid session; 402 — payment required / payment rejected.
  • 403 — authenticated but forbidden (role, over-cap, policy).
  • 409 — conflict (replayed nonce, duplicate idempotency key with different body).
  • 423 — account locked; 429 — rate limited (check Retry-After).
  • 502 — upstream (chain RPC, provider) failed; 503 — feature not configured on this deployment or rail paused.

Machine-readable codes

ParameterTypeDescription
amount_over_limitrequired403Amount exceeds the server-side per-transaction cap for the rail. The detail names the cap.
replay_rejectedrequired409 / 402The EIP-3009 nonce or quote was already used. Sign a fresh authorization — never resign the same nonce.
invalid_signaturerequired400 / 402Signature recovery did not match the payer (ECDSA and ERC-1271 both failed).
rail_pausedrequired503The rail is administratively paused. Retry after the status page clears.
authorization_expiredrequired400validBefore has passed. Sign a fresh authorization.
quote_expiredrequired402The 402 quote TTL elapsed. Re-request the resource for a fresh quote.
settlement_pendingoptional202Submitted on-chain but confirmation not yet observed. Poll pending.statusPath — do NOT sign a fresh authorization until it resolves.
service_unavailablerequired503The feature is not configured on this deployment (e.g. no on-chain relayer). Production never fabricates success.
reason_requiredrequired400Ops control changes require a typed reason for the audit trail.
unknown_pageoptional400Docs feedback referenced a path outside the documentation.

Retries

Retry 429 after the Retry-After value and 502 with backoff. Never blind-retry 402 replay rejections or 202 pending settlements — both can double-pay; follow the recovery path in the body instead.
Did this page help?
Edit this page on GitHub