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.
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.
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 }
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" }] }