Errors and limits

Stable error codes, bounded retries and pagination.

Last updated: Sep 30, 2026

Error contract

Management errors contain error (human-readable) and code (stable). Branch on code and HTTP status, not message text. Scope errors may include required and missing_scope; retryable responses may include retryAfter, retryable and Retry-After. Ownership failures return 404 to avoid revealing another wallet’s resources.

{ "error": "Insufficient scope", "code": "insufficient_scope", "required": "write" }
  • 400: invalid JSON, invalid request or a rejected destination.
  • 401: missing/invalid authentication or a rejected signature.
  • 402: insufficient credits or required slot capacity.
  • 403: insufficient Scope or another explicit permission restriction.
  • 404: missing or foreign resource.
  • 409: conflict, active idempotency lease, reused key or paused asset.
  • 410: expired topup draft.
  • 422: payment verification failed.
  • 429: rate limit or exhausted security fallback capacity.
  • 503: unavailable price, signer or transient dependency.

Retry safely

Honor Retry-After and use exponential backoff with jitter and a finite attempt budget. Repeat an idempotent operation with the same key and body. Do not retry authentication failures or denied permissions blindly. Never buy a second subscription merely because the first response timed out. Refresh a quote or draft only when the existing instruction is no longer usable.

Rate limits

Challenge and token exchange are limited to 30 requests per minute per IP. Topup drafts, subscription purchases and watch creation are limited to 10 per minute per wallet. Watch PATCH/DELETE allows 60 per minute; settings PUT and message preview allow 30 per minute; alert export allows 10 per hour. Limits apply before idempotency, including replays. A bounded process-local fallback is used when Redis is unavailable; security limits fail closed when fallback capacity is exhausted.

Recipient creation and target changes share 10 per hour per wallet. Confirmation mail has wallet and canonical-destination budgets. A throttled resend may return HTTP 200 with sent: false instead of 429; inspect that field. Recipient creation may report confirmationSent: false. A pending target remains unverified and cannot receive tests or alerts.

Account ledger and watch listing accept limit and an opaque cursor. Follow nextCursor until null; do not interpret or modify a cursor. Alert history instead uses zero-based page and pageSize with filters and sorting. Export is bounded to 5,000 rows. Do not mix the two pagination models.

curl 'https://ftso.app/api/x402/watches?limit=20' \
  -H 'authorization: Bearer fbx_READ_TOKEN'
# Follow the returned nextCursor, URL-encoded, on the next request.

The complete stable-code enum is published in the OpenAPI document. See operation reference for permissions.