FastPay API Reference

Generated 2026-10-23T13:59:34.274Z. This page is server-served static HTML mirroring the interactive documentation at /docs and /api, so crawlers and retrieval agents can extract the full endpoint and schema content without executing JavaScript.

Status

Conventions

Every response carries X-API-Version; /v1 takes no breaking change, a breaking change gets a /v2 path. Keys are fp_live_ or fp_test_ followed by 48 hex characters and carry scopes (['*'] or family:read/family:write entries — write implies read), an optional expiry and an optional IP allow-list. Idempotency-Key is honoured on every write made with an API key (not on /v2/x402/*, not on public no-key routes): for 24 hours (RELAY_IDEMPOTENCY_RETENTION_MS) a repeat with the same key, path and body replays the first answer; the same key with a different body answers 409 idempotency_parameter_mismatch; a repeat while the first is still running answers 409 idempotency_key_in_progress. List routes use cursor pagination (limit, starting_after, ending_before, has_more) with status and created[gte]/created[lte] filters where the route offers them. Every error body carries error_type, error_slug, error_message and a request_id equal to the X-Request-ID response header.

fp_test_ keys run entirely against sandbox rows and never reach a chain: a test key never sees live rows, a live key never sees sandbox rows. Checkout intents are simulated (simulated: true); confirm with any well-formed hash of the network's format — 0x followed by 64 f characters reports a failed payment, 64 e characters a pending one, anything else a paid one. Every payroll network works in the sandbox whatever the live switches say: payloads have the live format but nothing is broadcast, any non-empty proof.signature verifies a well-formed address, and any well-formed transaction ref settles the intent (a reference ending in _fail fails that one item). x402 always settles a real transfer, so sandbox keys are refused with 409 x402_sandbox_not_supported. Downstream effects are real: a sandbox payment that reaches paid fires the real webhook and posts to the real double-entry ledger.

Authentication

Limits

LimitValueConfiguration
Relay payments per request (mass payout)50RELAY_MAX_PAYMENTS_PER_REQUEST
Payroll items per batch500RELAY_PAYROLL_MAX_ITEMS_PER_BATCH
Payment request max expiry7 daysRELAY_PAYMENT_REQUEST_MAX_EXPIRY_MS
Checkout session expiry30 minutesRELAY_CHECKOUT_SESSION_EXPIRY_MS
Payroll authorization validity1 hour default, 1 day max (Bitcoin/Stacks multisig: 72 hours)RELAY_PAYROLL_AUTHORIZATION_VALIDITY_SECONDS
Webhook endpoints per tenant16RELAY_MAX_WEBHOOK_ENDPOINTS
Webhook delivery timeout15 secondsRELAY_WEBHOOK_TIMEOUT_MS
Webhook delivery attempts (in-process)5RELAY_WEBHOOK_MAX_ATTEMPTS
Idempotency key retention24 hoursRELAY_IDEMPOTENCY_RETENTION_MS
List page sizeDefault 20, configured maximumPAGINATION_DEFAULT_LIMIT / PAGINATION_MAX_LIMIT
Tenant daily payment countOperator configuredPer-tenant setting
Payout-creating writes rate limit60 / minute / tenantRELAY_DEFAULT_REQUESTS_PER_MINUTE
Public checkout routes rate limit30 / minute / IPRELAY_PUBLIC_PAYMENT_REQUEST_RATE_LIMIT
x402 facilitator rate limit1200 / minute / tenantRELAY_X402_REQUESTS_PER_MINUTE

Chains and assets

ChainAssetsAvailability
solanaSOL, USDC, USDT, USDe, USDS, USD1, FDUSD, EURC, PYUSD, USDG, AUSDPayroll: live in production (USDC). Checkout, channels, mass payout, x402: wired, not enabled.
ethereumETH, USDC, USDT, DAI, USDe, USDS, USD1, FDUSD, EURC, PYUSD, USDG, AUSDPayroll: gated (RELAY_PAYROLL_ENABLED_EVM). Checkout, channels, mass payout, x402: wired, not enabled.
baseETH, USDC, DAI, USDe, USDS, EURC, AUSDPayroll: gated (RELAY_PAYROLL_ENABLED_EVM). Checkout, channels, mass payout, x402: wired, not enabled.
polygonPOL, USDC, DAI, AUSDPayroll: gated (RELAY_PAYROLL_ENABLED_EVM). Checkout, channels, mass payout, x402: wired, not enabled.
arbitrumETH, USDC, DAI, USDe, USDS, PYUSD, AUSDPayroll: gated (RELAY_PAYROLL_ENABLED_EVM). Checkout, channels, mass payout, x402: wired, not enabled.
bnbBNB, USDC, USDT, DAI, USDe, USD1, FDUSD, AUSDPayroll: gated (RELAY_PAYROLL_ENABLED_EVM). Checkout, channels, mass payout, x402: wired, not enabled.
avalancheAVAX, USDC, USDT, USDe, EURC, AUSDPayroll: gated (RELAY_PAYROLL_ENABLED_EVM). Checkout, channels, mass payout, x402: wired, not enabled.
tronTRX, USDC, USDT, USD1Payroll: gated, refused while the authorizer is a funded key. Mass payout: gated (RELAY_TRON_UNSCOPED_KEY_RISK_ACCEPTED). x402: not built.
aptosAPT, USDC, USDT, USDePayroll: gated, no audited deployment. Checkout, mass payout, x402: wired, not enabled. Channels: not built.
suiSUI, USDC, FDUSD, AUSDPayroll: gated, no audited deployment. Checkout, mass payout: wired, not enabled. Channels, x402: not built.
tonTON, USDT, USDe, FDUSDPayroll: gated, no audited deployment (TON jetton custody exception applies). Checkout, mass payout: wired, not enabled. Channels, x402: not built.
bitcoinBTCPayroll: gated, native BTC via PSBT, FastPay signs nothing. Mass payout: wired, not enabled. No checkout, channels or x402.
algorand, stellar, hederaUSDCx402 facilitator-only (no seller route); no checkout, channels, mass payout or payroll.

Tenants & access

API keys, team members and roles, the payout approval policy, and the public discovery and sandbox routes. Every tenant route in this reference sits behind one of these keys.

GET /v1/api-keys/me — Get the calling key

Authentication: bearer. Status: documented.

Returns the key's id, prefix, scopes, mode (live or test) and the member it is issued to, if any.

Response example:

{ "id": "key_...", "prefix": "fp_live_", "scopes": ["payroll:write"], "member_id": "mem_...", "status": "active" }

POST /v1/api-keys/rotate — Rotate the calling key

Authentication: bearer. Status: documented.

Issues a successor key and starts a grace window during which both keys work.

Request example:

{ "grace_hours": 24 }

Response example:

{ "id": "key_...", "apiKey": "fp_live_...", "prefix": "fp_live_", "warning": "Store this key now; it will not be shown again." }
  • Only a key whose scopes are ["*"] may rotate itself (403 insufficient_scope otherwise).
  • grace_hours is 0 to 168; the old key answers 401 api_key_expired once it elapses.

GET /v1/members/me — Get the calling member

Authentication: bearer. Status: documented.

Returns the team member the calling key is issued to.

Response example:

{ "id": "mem_...", "name": "Jordan Lee", "email": "jordan@example.com", "role": "admin", "status": "active" }
  • 404 resource_missing when the key is bound to no member.

POST /v1/members — Create a team member

Authentication: bearer. Status: documented.

Adds a member with a role that caps every key issued to them.

Request example:

{ "name": "Payroll approver", "email": "approver@example.com", "role": "approver" }

Response example:

{ "id": "mem_...", "name": "Payroll approver", "email": "approver@example.com", "role": "approver", "status": "active" }
  • Needs owner or admin, except team bootstrap (no members yet), which may also create the first owner.
  • Errors: 400 invalid_name, invalid_email, invalid_role, invalid_status, email_in_use; 403 owner_role_required, member_required, capability_required.

GET /v1/members — List team members

Authentication: bearer. Status: documented.

Lists the tenant's members newest first, with cursor pagination and status/role filters.

Response example:

{ "object": "list", "data": [{ "id": "mem_...", "role": "owner", "status": "active" }], "has_more": false }

GET /v1/members/:id — Get a member

Authentication: bearer. Status: documented.

Returns one member of the calling tenant: id, name, email, role and status.

Response example:

{ "id": "mem_...", "role": "approver", "status": "active" }

POST /v1/members/:id — Update a member

Authentication: bearer. Status: documented.

Changes a member's name, role or status; a role change re-caps every key of that member at once, and suspension fails every one of their keys with 401 member_suspended.

Request example:

{ "role": "admin" }

Response example:

{ "id": "mem_...", "role": "admin", "status": "active" }
  • email cannot change (400 invalid_email).
  • 409 last_owner guards the tenant's last active owner.

POST /v1/members/:id/api-keys — Issue a key to a member

Authentication: bearer. Status: documented.

Mints an fp_live_ or fp_test_ key issued to the member, scoped to the member's role ceiling by default.

Request example:

{ "test": false, "scopes": ["payroll:write"], "expires_in_days": 365 }

Response example:

{ "id": "key_...", "apiKey": "fp_live_...", "prefix": "fp_live_", "scopes": ["payroll:write"], "warning": "Store this key now; it will not be shown again." }
  • The raw key is shown exactly once and stored only as a hash.

GET /v1/members/:id/api-keys — List a member's keys

Authentication: bearer. Status: documented.

Every key issued to the member, newest first, including expired and revoked ones. Never returns key material.

Response example:

{ "object": "list", "data": [{ "id": "key_...", "prefix": "fp_live_", "status": "revoked" }], "has_more": false }

GET /v1/payout-approval-policy — Get the approval policy

Authentication: bearer. Status: documented.

Returns the tenant's maker-checker policy for mass-payout jobs and payroll batches.

Response example:

{ "mode": "above", "approvals_required": 2, "thresholds": { "USDC": "10000" } }

POST /v1/payout-approval-policy — Set the approval policy

Authentication: bearer. Status: documented.

Replaces the tenant's policy; a field left out takes its default. Owner only (or the bootstrap key before any member exists).

Request example:

{ "mode": "above", "approvals_required": 2, "thresholds": { "USDC": "10000" } }

Response example:

{ "mode": "above", "approvals_required": 2, "thresholds": { "USDC": "10000" } }
  • mode is never, always or above.
  • Errors: 400 invalid_policy_mode, invalid_approvals_required, invalid_thresholds.

GET /v1/networks — List networks

Authentication: none. Status: documented.

Lists every network the relay validates a checkout request for, with a live flag that separates validation from fulfillment.

Response example:

{ "object": "list", "data": [{ "network": "base", "live": true }, { "network": "solana", "live": false }] }

GET /v1/currencies — List currencies

Authentication: none. Status: documented.

Lists the assets accepted per network, using the same live-flag semantics as /v1/networks.

Response example:

{ "object": "list", "data": [{ "currency": "USDC", "network": "base", "decimals": 6, "live": true }] }

GET /v1/rates — Get a rate

Authentication: none. Status: documented.

Returns the live indicative rate from the same engine checkout sessions quote with. Not a locked quote.

Request example:

?from=EUR&to=USDC

Response example:

{ "object": "rate", "from": "EUR", "to": "USDC", "rate": "1.08", "as_of": "2026-09-04T10:00:00.000Z" }
  • 400 parameter_missing without from or to.
  • 503 rate_unavailable when no live, recent or static rate exists for the pair.

GET /v1/gas-tokens — List fee-payment tokens

Authentication: none. Status: documented.

Lists the tokens a mass-payout network's paymaster will accept the fee in, for EVM networks.

Request example:

?network=base

Response example:

{ "object": "list", "data": [{ "currency": "USDC", "network": "base" }] }
  • 400 parameter_missing without network; 400 invalid_network when it is not a payout network.

GET /v1/balance — List balances

Authentication: bearer. Status: documented.

Lists each currency the tenant has a double-entry ledger account for, credited net of FastPay's gateway fee.

Response example:

{ "object": "list", "data": [{ "currency": "EUR", "available": "99.00", "pending": "0.00", "total": "99.00" }] }

GET /v1/balance/:currency — Get a balance

Authentication: bearer. Status: documented.

Returns one currency balance, creating the account on first read when needed.

Response example:

{ "currency": "EUR", "available": "99.00", "pending": "0.00", "total": "99.00" }

GET /v1/balance/transactions — List ledger transactions

Authentication: bearer. Status: documented.

Lists immutable ledger entries for the tenant, most recent first, with an optional ?format=csv export.

Response example:

{ "object": "list", "data": [{ "id": "le_...", "direction": "credit", "amount": "99.00", "currency": "EUR" }], "has_more": false }

GET /v1/sandbox — Get sandbox status

Authentication: bearer. Status: documented.

Confirms the calling key is a test key and reports sandbox row counts.

Response example:

{ "mode": "test", "payment_requests": 4, "payroll_batches": 1 }

POST /v1/sandbox/reset — Reset sandbox data

Authentication: bearer. Status: documented.

Clears every sandbox row owned by the tenant's test key so a fresh integration test run starts clean.

Response example:

{ "success": true }

POST /v1/sandbox/deposits — Simulate a channel deposit

Authentication: bearer. Status: documented.

Credits a simulated deposit to a sandbox payment channel, since the sandbox never watches a real chain.

Request example:

{ "payment_channel_id": "pc_...", "network": "ethereum", "currency": "USDC", "amount": "25.00" }

Response example:

{ "id": "dep_...", "status": "detected", "amount": "25.00", "currency": "USDC" }

Payments gateway — checkout

The customer's own wallet signs and broadcasts; FastPay's only signature is a fund-free paymentAuthorizer co-signature. The relay re-derives every payment from the chain before marking it paid — it never trusts a reported hash. Funds go straight to the merchant's recipient address.

POST /v1/payment-requests — Create a payment request

Authentication: bearer. Status: documented.

Creates a single-use, crypto-priced invoice with an expiry of up to 7 days (RELAY_PAYMENT_REQUEST_MAX_EXPIRY_MS).

Request example:

{
  "reference": "order-1042", "network": "base", "currency": "USDC", "amount": "25.00",
  "recipient": "0x1111111111111111111111111111111111111111",
  "expiresAt": "2026-09-05T12:00:00Z",
  "callbackUrl": "https://merchant.example/webhooks/fastpay"
}

Response example:

{ "id": "pr_...", "status": "pending", "checkoutUrl": "https://relay.example/checkout?id=pr_...", "qr": "data:image/png;base64,..." }
  • Idempotency-Key is honoured: same key and body replays the first answer; a different body answers 409 idempotency_parameter_mismatch.
  • recipient omitted uses the tenant's registered payout wallet (none: 400 missing_recipient).

GET /v1/payment-requests — List payment requests

Authentication: bearer. Status: documented.

Lists the tenant's payment requests, newest first, with cursor pagination and status/network filters.

Response example:

{ "object": "list", "data": [{ "id": "pr_...", "status": "paid" }], "has_more": false }

GET /v1/payment-requests/:requestId — Get a payment request

Authentication: bearer. Status: documented.

Returns the merchant's full tenant-scoped payment-request record.

Response example:

{ "id": "pr_...", "reference": "order-1042", "status": "pending" }

GET /v1/payment-requests/:requestId/public — Get a public payment request

Authentication: none. Status: documented.

Returns the sanitized fields a hosted checkout needs, with no tenant ids or private metadata. The unguessable id is the capability.

Response example:

{ "id": "pr_...", "network": "base", "currency": "USDC", "amount": "25.00", "recipient": "0x...", "status": "pending" }

POST /v1/payment-requests/:requestId/intent — Create a payment intent

Authentication: none. Status: documented.

Builds unsigned transaction data for the payer's connected wallet to sign and broadcast; FastPay never signs it.

Request example:

{ "payerAddress": "0x2222222222222222222222222222222222222222" }

Response example:

{ "to": "0x...", "data": "0x...", "value": "0x0", "fee": "1.25", "total": "26.25" }

POST /v1/payment-requests/:requestId/confirm — Confirm a payment

Authentication: none. Status: documented.

Verifies the exact on-chain transfer before marking the request paid; a reported hash is never trusted by itself.

Request example:

{ "txHash": "0x9f2b..." }

Response example:

{ "status": "paid", "txHash": "0x...", "verification": "verified" }
  • 202 for a pending chain confirmation; 422 payment_not_verified for a mismatch.
  • The first successful confirmation fires payment_request.paid.

GET /v1/payment-requests/:requestId/qr — Get a payment QR

Authentication: none. Status: documented.

Returns a QR image for the checkout URL or a Solana Pay URL, size 128 to 1024 pixels.

Response example:

image/png

GET / POST /v1/payment-requests/:requestId/solana-pay — Solana Pay transfer request

Authentication: none. Status: documented.

GET returns the Solana Pay label/icon; POST returns the serialized transaction for a Solana Pay wallet to sign.

Response example:

{ "transaction": "base64...", "message": "FastPay" }

POST /v1/payment-links — Create a payment link

Authentication: bearer. Status: documented.

Creates a reusable link; each visitor who opens it gets their own payment request.

Request example:

{ "network": "base", "currency": "USDC", "amount": "10.00", "reference_prefix": "donation", "max_uses": 100 }

Response example:

{ "id": "plink_...", "url": "https://relay.example/pay/plink_...", "qr": "data:image/png;base64,...", "available": true }

GET /v1/payment-links — List payment links

Authentication: bearer. Status: documented.

Lists the tenant's payment links, newest first, with cursor pagination.

Response example:

{ "object": "list", "data": [{ "id": "plink_...", "available": true }], "has_more": false }

GET /v1/payment-links/:linkId — Get a payment link

Authentication: bearer. Status: documented.

Returns one tenant-scoped payment link.

Response example:

{ "id": "plink_...", "available": true }

POST /v1/payment-links/:linkId/deactivate — Deactivate a link

Authentication: bearer. Status: documented.

Stops the link from accepting new payments.

Response example:

{ "id": "plink_...", "available": false }

POST /v1/payment-links/:linkId/activate — Reactivate a link

Authentication: bearer. Status: documented.

Reopens a deactivated link.

Response example:

{ "id": "plink_...", "available": true }

GET /v1/payment-links/:linkId/public — Get a public payment link

Authentication: none. Status: documented.

Returns the sanitized fields a hosted link page needs.

Response example:

{ "id": "plink_...", "network": "base", "currency": "USDC" }

POST /v1/payment-links/:linkId/sessions — Start a link session

Authentication: none. Status: documented.

Creates a fresh payment request for one visitor of a reusable link.

Response example:

{ "id": "pr_...", "status": "pending" }

GET /v1/payment-links/:linkId/qr — Get a link QR

Authentication: none. Status: documented.

Returns a QR image for the link URL.

Response example:

image/png

GET / POST /v1/payment-links/:linkId/solana-pay — Solana Pay for a link

Authentication: none. Status: documented.

Solana Pay transfer-request protocol for a fixed-amount link, mirroring the payment-request routes.

Response example:

{ "transaction": "base64..." }

POST /v1/checkout/sessions — Create a checkout session

Authentication: bearer. Status: beta.

Creates a fiat-priced (EUR or USD) session, resolves the tenant's registered payout wallet and quotes a settlement currency.

Request example:

{
  "amount": "100.00", "currency": "EUR", "network": "base", "settlement_currency": "USDC",
  "success_url": "https://merchant.example/success", "cancel_url": "https://merchant.example/cancel"
}

Response example:

{ "id": "cs_...", "status": "open", "payment_amount": "108.00", "payment_currency": "USDC", "exchange_rate": "1.08", "payment_url": "https://relay.example/checkout?id=cs_...", "expires_at": "..." }
  • Default expiry 30 minutes (RELAY_CHECKOUT_SESSION_EXPIRY_MS).
  • Needs a payout wallet registered for the network (none: 400 missing_recipient).

GET /v1/checkout/sessions — List checkout sessions

Authentication: bearer. Status: beta.

Lists the tenant's sessions, newest first.

Response example:

{ "object": "list", "data": [{ "id": "cs_...", "status": "open" }] }

GET /v1/checkout/sessions/:id — Get a checkout session

Authentication: bearer. Status: beta.

Returns a session and its linked payments.

Response example:

{ "id": "cs_...", "payments": [{ "id": "pay_...", "status": "created" }] }

POST /v1/checkout/sessions/:id/expire — Expire a checkout session

Authentication: bearer. Status: beta.

Closes an open session early.

Response example:

{ "id": "cs_...", "status": "expired" }

GET /v1/payments — List payments

Authentication: bearer. Status: beta.

Lists the tenant's payments, newest first.

Response example:

{ "object": "list", "data": [{ "id": "pay_...", "status": "paid" }] }

GET /v1/payments/:id — Get a payment

Authentication: bearer. Status: beta.

Returns the full lifecycle: every recorded status transition, the financial breakdown and known chain data.

Response example:

{ "id": "pay_...", "status": "paid", "lifecycle": [{ "status": "created", "at": "..." }, { "status": "paid", "at": "..." }], "blockchain": { "txHash": "0x...", "confirmations": 12 } }

GET /v1/payments/:id/transactions — List payment transactions

Authentication: bearer. Status: beta.

Exposes the on-chain transaction records stored for a payment, including confirmation counts and reorg detection.

Response example:

{ "object": "list", "data": [{ "txHash": "0x...", "confirmations": 12, "reorged": false }] }

POST /v1/payments/:id/refunds — Create a refund

Authentication: bearer. Status: beta.

Builds an unsigned refund transaction for the merchant's own wallet to sign; only checkout_session payments can be refunded.

Request example:

{ "amount": "25.00", "reason": "customer_request" }

Response example:

{ "id": "re_...", "status": "pending", "transaction": { "to": "0x...", "data": "0x..." } }

GET /v1/refunds — List refunds

Authentication: bearer. Status: beta.

Lists the tenant's refunds, newest first.

Response example:

{ "object": "list", "data": [{ "id": "re_...", "status": "confirmed" }] }

GET /v1/refunds/:id — Get a refund

Authentication: bearer. Status: beta.

Returns one tenant-scoped refund.

Response example:

{ "id": "re_...", "status": "confirmed", "tx_hash": "0x..." }

POST /v1/refunds/:id/confirm — Confirm a refund

Authentication: bearer. Status: beta.

Verifies the broadcast refund on chain and posts the matching ledger debit.

Request example:

{ "txHash": "0x..." }

Response example:

{ "id": "re_...", "status": "confirmed" }

GET /v1/reconciliation — Reconciliation report

Authentication: bearer. Status: beta.

Cross-references business, on-chain and ledger state for paid payments and flags discrepancies, including reorgs.

Response example:

{ "window_days": 7, "checked": 128, "discrepancies": [] }

Customers & payment channels

Reusable deposit addresses, one per requested network, minted in a Turnkey sub-organization rooted by the tenant's own registered key. The relay keeps a policy-limited signing user there; no relay route moves channel funds onward.

POST /v1/customers — Create a customer

Authentication: bearer. Status: beta.

Creates a lightweight customer record; not a KYC or identity object.

Request example:

{ "email": "payer@example.com", "name": "Example Payer", "country": "US" }

Response example:

{ "id": "cus_...", "email": "payer@example.com" }

GET /v1/customers — List customers

Authentication: bearer. Status: beta.

Lists customer records for the tenant.

Response example:

{ "object": "list", "data": [{ "id": "cus_..." }] }

GET /v1/customers/:id — Get a customer

Authentication: bearer. Status: beta.

Returns one tenant-scoped customer record.

Response example:

{ "id": "cus_...", "email": "payer@example.com" }

POST /v1/payment-channels — Create a payment channel

Authentication: bearer. Status: beta.

Mints one dedicated deposit address per requested network for a customer, server-side through Turnkey.

Request example:

{ "customer_id": "cus_...", "networks": ["ethereum", "tron"] }

Response example:

{ "id": "pc_...", "status": "active", "addresses": [{ "network": "ethereum", "address": "0x..." }] }
  • 409 no_tenant_root_key if the tenant has no registered custody root key.
  • Generated does not mean monitored: no chain watcher links deposits to the ledger on every network.

GET /v1/payment-channels — List payment channels

Authentication: bearer. Status: beta.

Lists payment channels for the tenant.

Response example:

{ "object": "list", "data": [{ "id": "pc_...", "status": "active" }] }

GET /v1/payment-channels/:id — Get a payment channel

Authentication: bearer. Status: beta.

Returns one tenant-scoped payment channel and its addresses.

Response example:

{ "id": "pc_...", "addresses": [{ "network": "ethereum", "address": "0x..." }] }

POST /v1/payment-channels/:id/close — Close a payment channel

Authentication: bearer. Status: beta.

Closes a payment channel so it is no longer active.

Response example:

{ "id": "pc_...", "status": "closed" }

GET /v1/payment-channels/:id/deposits — List a channel's deposits

Authentication: bearer. Status: beta.

Lists deposits detected on one payment channel's addresses.

Response example:

{ "object": "list", "data": [{ "id": "dep_...", "status": "detected" }] }

GET /v1/deposits — List deposits

Authentication: bearer. Status: beta.

Lists deposits across every channel of the tenant.

Response example:

{ "object": "list", "data": [{ "id": "dep_...", "amount": "25.00" }] }

GET /v1/deposits/:id — Get a deposit

Authentication: bearer. Status: beta.

Returns one tenant-scoped deposit.

Response example:

{ "id": "dep_...", "amount": "25.00", "status": "detected" }

Webhooks

Deliveries are POST requests with x-webhook-timestamp, x-webhook-signature (HMAC-SHA256 of timestamp + "." + rawBody) and x-webhook-secret headers. Register an endpoint for chosen events, or rely on a per-object callback URL signed with the tenant webhook secret.

GET /v1/webhooks/event-types — List event types

Authentication: none. Status: documented.

The public catalogue of every event type the relay can send.

Response example:

{ "object": "list", "data": ["payment_request.paid", "checkout_session.paid", "payroll_item.completed", "payroll_batch.closed"] }

GET /v1/webhooks/egress-ips — List egress IPs

Authentication: none. Status: documented.

The IP addresses webhook deliveries come from, for firewall allow-listing.

Response example:

{ "object": "list", "data": ["203.0.113.10"] }

POST /v1/webhooks/secret/rotate — Rotate the tenant webhook secret

Authentication: bearer. Status: documented.

Issues a new whsec_... secret for per-object callback URLs and keeps the previous one signing for a grace window.

Request example:

{ "expires_in_hours": 24 }

Response example:

{ "secret": "whsec_...", "previous_secret_expires_at": "..." }
  • expires_in_hours is 0 to 72, default 24.

POST /v1/webhook-endpoints — Register a webhook endpoint

Authentication: bearer. Status: documented.

Registers an endpoint for chosen events; omitting enabled_events subscribes to every event (['*']). Up to 16 per tenant (RELAY_MAX_WEBHOOK_ENDPOINTS).

Request example:

{ "url": "https://merchant.example/webhooks/fastpay", "enabled_events": ["payment_request.paid", "payroll_item.completed"] }

Response example:

{ "id": "we_...", "url": "https://merchant.example/webhooks/fastpay", "secret": "whsec_...", "enabled_events": ["payment_request.paid"] }
  • secret is returned only when the endpoint is created or its secret rotated.

GET /v1/webhook-endpoints — List webhook endpoints

Authentication: bearer. Status: documented.

Lists the tenant's registered endpoints.

Response example:

{ "object": "list", "data": [{ "id": "we_...", "status": "enabled" }] }

GET /v1/webhook-endpoints/:id — Get a webhook endpoint

Authentication: bearer. Status: documented.

Returns one tenant-scoped endpoint.

Response example:

{ "id": "we_...", "url": "https://merchant.example/webhooks/fastpay" }

POST /v1/webhook-endpoints/:id — Update a webhook endpoint

Authentication: bearer. Status: documented.

Changes the URL, enabled events or status of an endpoint.

Request example:

{ "enabled_events": ["payroll_batch.closed"] }

Response example:

{ "id": "we_...", "enabled_events": ["payroll_batch.closed"] }

DELETE /v1/webhook-endpoints/:id — Remove a webhook endpoint

Authentication: bearer. Status: documented.

Deletes a registered endpoint.

Response example:

{ "success": true }

POST /v1/webhook-endpoints/:id/rotate-secret — Rotate an endpoint secret

Authentication: bearer. Status: documented.

Issues a new secret for one endpoint; returned only at rotation time.

Response example:

{ "id": "we_...", "secret": "whsec_..." }

GET /v1/webhook-deliveries — List webhook deliveries

Authentication: bearer. Status: documented.

Shows every delivery attempted for the tenant, with the receiver's real HTTP status and a capped response body.

Response example:

{ "object": "list", "data": [{ "id": "whd_...", "type": "payment_request.paid", "status": "delivered", "attempts": 1 }], "has_more": false }

GET /v1/webhook-deliveries/:id — Get a webhook delivery

Authentication: bearer. Status: documented.

Returns one delivery's full payload and attempt history.

Response example:

{ "id": "whd_...", "status": "delivered", "response_status": 200 }

POST /v1/webhook-deliveries/:id/redeliver — Redeliver a webhook

Authentication: bearer. Status: documented.

Retries a specific delivery on demand instead of waiting for the periodic sweep. 409 if a redelivery for that row is already in flight.

Response example:

{ "id": "whd_...", "status": "pending", "attempts": 2 }

Accounting & e-commerce integrations

Connect an accounting provider (OAuth with PKCE, GL mapping, record sync) or an e-commerce platform whose orders become checkout sessions paid to the tenant's payout wallet.

GET /v1/accounting/:provider/connect — Start accounting connection

Authentication: bearer. Status: documented.

Starts the provider's OAuth (PKCE) flow.

POST /v1/accounting/:provider/callback — Complete accounting connection

Authentication: bearer. Status: documented.

Completes the OAuth flow.

GET /v1/accounting/:provider/mappings — Get GL mapping

Authentication: bearer. Status: documented.

Returns provider, connected, expiresAt and the current GL mapping.

Response example:

{ "provider": "xero", "connected": true, "expiresAt": "...", "mapping": {} }
  • 400 invalid_provider.

POST /v1/accounting/:provider/mappings — Replace GL mapping

Authentication: bearer. Status: documented.

Replaces the GL mapping.

POST /v1/accounting/:provider/sync — Sync a record

Authentication: bearer. Status: documented.

Pushes one record to the provider.

GET /v1/ecommerce/:platform/settings — Get platform settings

Authentication: bearer. Status: documented.

Returns platform, connected (a webhook secret is stored; never returned), config and webhook_url.

  • 400 invalid_platform.

POST /v1/ecommerce/:platform/settings — Save platform settings

Authentication: bearer. Status: documented.

Stores the platform's webhook secret and configuration.

POST /v1/ecommerce/:platform/orders — Create checkout from an order

Authentication: bearer. Status: documented.

Turns platform order data into a checkout session through the same quote-and-create step as POST /v1/checkout/sessions; recipient is the tenant's payout wallet for the network.

POST /v1/ecommerce/:platform/webhook/:tenantId — Platform webhook receiver

Authentication: none. Status: documented.

Inbound webhook called by the platform itself, verified with the stored platform secret.

x402 (Beta)

x402 is built and covered by tests against real signatures and transactions, but no end-to-end x402 settlement has been broadcast on a mainnet yet. Support is by design and by test, not by a recorded live payment. Talk to FastPay before routing real traffic. Protocol v2, exact scheme, both the facilitator (/v2/x402/*) and the seller route (/v1/x402/pay/:requestId). Full guide at /docs/x402.

GET /v2/x402/supported — x402 supported

Authentication: none. Status: beta.

Supported kinds (one per enabled network), extensions and signers. extra carries feePayer on Solana, Algorand, Aptos and Hedera; areFeesSponsored on Stellar; nothing on EVM.

  • Auth: None. Rate limit: Per IP, 30/min.
  • Sandbox keys are refused (409 x402_sandbox_not_supported): x402 always settles a real transfer.

POST /v2/x402/verify — x402 verify

Authentication: bearer. Status: beta.

200 { isValid, payer, invalidReason? }. Never signs anything.

  • Auth: Live API key with checkout:write. Rate limit: Per tenant, 1200/min.
  • Sandbox keys are refused (409 x402_sandbox_not_supported): x402 always settles a real transfer.

POST /v2/x402/settle — x402 settle

Authentication: bearer. Status: beta.

200 { success, payer, transaction, network, errorReason? }. transaction is empty only when nothing was broadcast.

  • Auth: Live API key with checkout:write. Rate limit: Per tenant, 1200/min.
  • Sandbox keys are refused (409 x402_sandbox_not_supported): x402 always settles a real transfer.

GET/POST /v1/x402/pay/:requestId — x402 :requestId

Authentication: none. Status: beta.

402 with PAYMENT-REQUIRED when unpaid; 200 { id, status, txHash, payer, network } on settlement; 200 { id, status, txHash, payer } when already paid; 404 / 409 / 410 otherwise.

  • Auth: None. Rate limit: Per IP, 30/min.
  • Sandbox keys are refused (409 x402_sandbox_not_supported): x402 always settles a real transfer.

Mass disbursement — client-signed (payroll)

The payer registers its own wallet, proves control, submits a batch, and FastPay co-signs a fund-free authorization and serves unsigned transactions. The payer's wallet signs and broadcasts; FastPay never holds a key over the payer's funds, with one exception: a TON jetton payment that bounces stays in the disburser's own jetton wallet until FastPay's admin moves it.

POST /v1/payroll-payer-wallets — Register a payer wallet

Authentication: bearer. Status: documented.

Registers the wallet that will pay a batch and returns a challenge to sign. Only an owner or admin member's key may call it.

Request example:

{ "network": "solana", "address": "9Wz...", "label": "Payroll treasury" }

Response example:

{ "id": "pyw_...", "status": "pending_verification", "challenge": "fastpay-payroll-verify:...", "challenge_expires_at": "..." }
  • 409 payer_wallet_is_fastpay_key refuses a wallet FastPay holds a key for.
  • 409 payer_wallet_delegated refuses a Solana PDA or vault address.
  • 503 payroll_network_not_enabled when the family switch is off for a live key.

POST /v1/payroll-payer-wallets/:id/challenge — Refresh a wallet challenge

Authentication: bearer. Status: documented.

Issues a new single-use challenge after the previous one expired.

Response example:

{ "challenge": "fastpay-payroll-verify:...", "challenge_expires_at": "..." }

POST /v1/payroll-payer-wallets/:id/verify — Verify a payer wallet

Authentication: bearer. Status: documented.

Submits the wallet's signature over the challenge text to prove control.

Request example:

{ "proof": { "signature": "5gH..." } }

Response example:

{ "id": "pyw_...", "status": "verified" }
  • Errors: 400 invalid_proof, invalid_payer_signature; 409 payer_challenge_missing; 410 payer_challenge_expired.

GET /v1/payroll-payer-wallets — List payer wallets

Authentication: bearer. Status: documented.

Lists the tenant's registered payer wallets.

Response example:

{ "object": "list", "data": [{ "id": "pyw_...", "status": "verified" }] }

GET /v1/payroll-payer-wallets/:id — Get a payer wallet

Authentication: bearer. Status: documented.

Returns one tenant-scoped payer wallet.

Response example:

{ "id": "pyw_...", "network": "solana", "status": "verified" }

DELETE /v1/payroll-payer-wallets/:id — Revoke a payer wallet

Authentication: bearer. Status: documented.

Revokes a wallet so it can no longer create batches. Does not revoke authorizations already issued.

Response example:

{ "id": "pyw_...", "status": "revoked" }

POST /v1/payroll-batches — Create a payroll batch

Authentication: bearer. Status: documented.

Submits up to 500 items (RELAY_PAYROLL_MAX_ITEMS_PER_BATCH) to be paid from a verified payer wallet.

Request example:

{
  "network": "solana", "currency": "USDC", "payer_wallet_id": "pyw_...", "reference": "payroll-october",
  "items": [{ "reference": "emp-001-october", "recipient_address": "9Wz...", "amount": "150.00" }]
}

Response example:

{ "id": "pyb_...", "status": "awaiting_approval" }
  • Errors: 400 invalid_items (details.problems lists every row), 400 too_many_items, 409 payer_wallet_not_verified, 409 duplicate_item_reference.

GET /v1/payroll-batches — List payroll batches

Authentication: bearer. Status: documented.

Lists the tenant's payroll batches, newest first.

Response example:

{ "object": "list", "data": [{ "id": "pyb_...", "status": "open" }] }

GET /v1/payroll-batches/:id — Get a payroll batch

Authentication: bearer. Status: documented.

Returns one tenant-scoped batch.

Response example:

{ "id": "pyb_...", "status": "open" }

GET /v1/payroll-batches/:id/items — List batch items

Authentication: bearer. Status: documented.

Lists the items of one batch and their per-item status.

Response example:

{ "object": "list", "data": [{ "reference": "emp-001-october", "status": "completed" }] }

POST /v1/payroll-batches/:id/approve — Approve a batch

Authentication: bearer. Status: documented.

Approves a batch under maker-checker. Needs owner, admin or approver; the creating member is refused with 403 self_approval_forbidden.

Response example:

{ "id": "pyb_...", "status": "open" }

POST /v1/payroll-batches/:id/reject — Reject a batch

Authentication: bearer. Status: documented.

Rejects a batch awaiting approval.

Request example:

{ "reason": "duplicate run" }

Response example:

{ "id": "pyb_...", "status": "rejected" }

POST /v1/payroll-batches/:id/cancel — Cancel a batch

Authentication: bearer. Status: documented.

Stops further signatures; cannot revoke an authorization already handed out.

Response example:

{ "id": "pyb_...", "status": "cancelled" }

POST /v1/payroll-batches/:id/intents — Create a payroll intent

Authentication: bearer. Status: documented.

FastPay co-signs a fund-free authorization and returns unsigned transactions for the payer wallet to sign and broadcast.

Request example:

{ "valid_for_seconds": 3600 }

Response example:

{ "id": "pyi_...", "transactions": [{ "items": [], "payloads": [{ "transaction": "base64..." }] }] }
  • Production caps a Solana intent at 7 items (single mode); call again after each intent settles.
  • 409 payroll_intent_already_live: one open intent per payer wallet, network and token at a time.

GET /v1/payroll-batches/:id/intents — List intents

Authentication: bearer. Status: documented.

Lists the intents issued for one batch.

Response example:

{ "object": "list", "data": [{ "id": "pyi_...", "status": "settled" }] }

GET /v1/payroll-batches/:id/intents/:intentId — Get an intent

Authentication: bearer. Status: documented.

Returns one intent.

Response example:

{ "id": "pyi_...", "status": "pending" }

POST /v1/payroll-batches/:id/intents/:intentId/requote — Requote an intent

Authentication: bearer. Status: documented.

Bitcoin only: rebuilds a PSBT at the current fee rate before it is signed.

Response example:

{ "id": "pyi_...", "payloads": [{ "psbt": "..." }] }

POST /v1/payroll-batches/:id/fee-quotes — Get a Bitcoin fee quote

Authentication: bearer. Status: documented.

Bitcoin only: quotes the network fee before intent creation.

Response example:

{ "id": "pfs_...", "fee_rate": "12" }

GET /v1/payroll-batches/:id/fee-quotes/:feeQuoteId — Get a fee quote

Authentication: bearer. Status: documented.

Returns one Bitcoin fee quote.

Response example:

{ "id": "pfs_...", "status": "open" }

POST /v1/payroll-batches/:id/confirm — Confirm a payroll intent

Authentication: bearer. Status: documented.

Reports a broadcast transaction reference; FastPay verifies the outcome from the disburser's own events. Optional — a settlement sweep also finds it.

Request example:

{ "tx_hash": "5sX..." }

Response example:

{ "status": "settled", "items": [{ "reference": "emp-001-october", "status": "completed" }] }

POST /v1/payroll-batches/:id/sponsor — Request fee sponsorship

Authentication: bearer. Status: documented.

Stacks only, opt-in: FastPay adds a SIP-005 fee-payer signature and broadcasts a transaction the payer already signed. Switched off today (503 sponsorship_screening_unavailable).

Response example:

{ "status": "sponsored", "tx_hash": "0x..." }

Mass disbursement — relay-signed (interim, not offered)

A tenant funds its own account; FastPay's relay signs every payout with a Turnkey session key scoped to that tenant. On EVM that key is the sole owner of the tenant's ERC-4337 smart account; on Solana a guarded vault or session-key wallet; on Aptos, Sui, TON, Bitcoin and Litecoin the payout wallet is itself the session key. Tron is refused unless RELAY_TRON_UNSCOPED_KEY_RISK_ACCEPTED=true.

POST /v1/instructions — Create payout instructions

Authentication: bearer. Status: documented.

Accepts a batch (up to RELAY_MAX_PAYMENTS_PER_REQUEST, default 50) for asynchronous execution and returns a job id.

Request example:

{
  "network": "ethereum",
  "payments": [{ "ALLOCATION_ID": "payroll-2026-09-0001", "CHAIN": "ETH", "WALLET": "0x...", "CURRENCY": "USDC", "AMOUNT": 1250.00 }],
  "callbackUrl": "https://client.example.com/webhooks/fastpay"
}

Response example:

{ "jobId": "bc163be2-...", "status": "accepted" }
  • EVM pays ERC-20 tokens only; native coin is 400 native_currency_unattended_unsupported.
  • 402 daily_payment_cap_exceeded past the tenant's daily cap.
  • 503 service_paused (error_slug null) while the kill switch is on.

GET /v1/jobs — List payout jobs

Authentication: bearer. Status: documented.

Lists the tenant's mass-payout jobs, newest first, with cursor pagination.

Response example:

{ "object": "list", "data": [{ "jobId": "bc163be2-...", "status": "processing" }], "has_more": false }

GET /v1/jobs/:jobId — Get a payout job

Authentication: bearer. Status: documented.

Returns the tenant-scoped job, lifecycle status and per-payment results.

Response example:

{ "jobId": "bc163be2-...", "status": "processing", "payments": [{ "ALLOCATION_ID": "payroll-2026-09-0001", "status": "completed" }] }

POST /v1/jobs/:jobId/approve — Approve a payout job

Authentication: bearer. Status: documented.

Approves a job awaiting approval under the tenant's approval policy. Self-approval is refused (403 self_approval_forbidden).

Response example:

{ "jobId": "bc163be2-...", "status": "accepted" }

POST /v1/jobs/:jobId/reject — Reject a payout job

Authentication: bearer. Status: documented.

Rejects a job awaiting approval.

Request example:

{ "reason": "duplicate run" }

Response example:

{ "jobId": "bc163be2-...", "status": "rejected" }

POST /v1/jobs/:jobId/cancel — Cancel a payout job

Authentication: bearer. Status: documented.

Cancels a job only while accepted, awaiting_onboarding or awaiting_approval; once execution starts it cannot be cancelled.

Response example:

{ "jobId": "bc163be2-...", "status": "cancelled" }

GET /v1/jobs/:jobId/payouts — List payouts for a job

Authentication: bearer. Status: documented.

Lists the payout entries belonging to one job, newest first, with cursor pagination.

Response example:

{ "object": "list", "data": [{ "id": "po_...", "status": "completed" }], "has_more": false }

GET /v1/payouts/:id — Get a payout

Authentication: bearer. Status: documented.

Returns one payout, addressable as its own resource.

Response example:

{ "id": "po_...", "job_id": "bc163be2-...", "status": "completed", "tx_hash": "0x..." }

POST /v1/recipients — Create a recipient

Authentication: bearer. Status: documented.

Creates a reusable beneficiary with per-network addresses, so future payments can reference recipient_id instead of a raw wallet.

Request example:

{ "name": "Jordan Lee", "type": "individual", "addresses": [{ "network": "ethereum", "address": "0x..." }] }

Response example:

{ "id": "rcp_...", "name": "Jordan Lee", "status": "active" }

GET /v1/recipients — List recipients

Authentication: bearer. Status: documented.

Lists the tenant's recipients, newest first.

Response example:

{ "object": "list", "data": [{ "id": "rcp_...", "status": "active" }] }

GET /v1/recipients/:id — Get a recipient

Authentication: bearer. Status: documented.

Returns one tenant-scoped recipient.

Response example:

{ "id": "rcp_...", "name": "Jordan Lee" }

POST /v1/recipients/:id — Update a recipient

Authentication: bearer. Status: documented.

Updates a recipient's name, type, email or metadata.

Request example:

{ "email": "jordan@example.com" }

Response example:

{ "id": "rcp_...", "email": "jordan@example.com" }

PUT /v1/recipients/:id/addresses/:network — Set a recipient address

Authentication: bearer. Status: documented.

Adds or replaces the address a recipient holds for one network.

Request example:

{ "address": "0x..." }

Response example:

{ "id": "rcp_...", "addresses": [{ "network": "ethereum", "address": "0x..." }] }

DELETE /v1/recipients/:id/addresses/:network — Remove a recipient address

Authentication: bearer. Status: documented.

Removes a recipient's address for one network.

Response example:

{ "success": true }

POST /v1/recipients/:id/archive — Archive a recipient

Authentication: bearer. Status: documented.

Archives a recipient so new payments cannot reference it.

Response example:

{ "id": "rcp_...", "status": "archived" }

POST /v1/recipients/:id/activate — Reactivate a recipient

Authentication: bearer. Status: documented.

Reactivates an archived recipient.

Response example:

{ "id": "rcp_...", "status": "active" }

GET /v1/recipients/:id/payouts — List a recipient's payouts

Authentication: bearer. Status: documented.

Lists payouts previously sent to this recipient.

Response example:

{ "object": "list", "data": [{ "id": "po_...", "status": "completed" }] }

Roadmap — not built

Other machine-readable resources