SPayFi | API Reference

Build with the API

API Reference

Create and manage payments programmatically. All amounts are in cents (e.g. 1000 = $10.00) and all timestamps are ISO 8601 UTC.

Authentication

All payments authenticate with an API key in the request header.

Method Authentication Used for
API Key X-API-Key: pk_<secret> Payments (server-to-server)

Create a payment

Creates a payment and returns a hosted checkout URL (<code>payment_url</code>) redeemable with the <code>payment_token</code>. Your customer picks the network and stablecoin at checkout; the deposit address is created then. The minimum amount is 1 cent. Optionally pass <code>chain</code> and <code>stablecoin</code> together to pre-create a specific deposit address.

POST /api/v1/payments/ API key

Request fields

amount_fiat int required

Amount in cents (min: 1).

currency string optional

ISO 4217 code. Default: USD.

chain string optional

Optional. Format evm:<chainId> or svm:<chainId>. Must be sent with stablecoin.

stablecoin string optional

Optional. Token symbol, e.g. USDC, USDT. Must be sent with chain.

idempotency_key string optional

Optional key to safely retry a request.

422: Validation error. 404/401: unauthenticated or invalid API key.

curl -X POST http://localhost:8000/api/v1/payments/ \
  -H "X-API-Key: pk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount_fiat": 1000,
    "currency": "USD"
  }'
Returns 201
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payment_token": "k7vP...random9xU",
  "status": "pending",
  "amount_fiat": 1000,
  "currency": "USD",
  "chain": null,
  "stablecoin": null,
  "deposit_address": null,
  "deposit_addresses": [],
  "tx_hash": null,
  "amount_received": null,
  "confirmations_seen": 0,
  "created_at": "2026-01-15T10:30:00Z",
  "expires_at": null,
  "payment_url": "https://pay.yourdomain.com/pay/k7vP...random9xU/"
}

List payments

Lists payments for the authenticated app. Filter by status and paginate.

GET /api/v1/payments/ API key

Query params

status

Filter by payment status.

page

Page number (default 1).

page_size

Items per page (default 20).

-

curl "http://localhost:8000/api/v1/payments/?status=confirmed&page=1&page_size=20" \
  -H "X-API-Key: pk_live_abc123..."
Returns 200
{
  "items": [{ "id": "a1b2...", "status": "confirmed", "amount_fiat": 1000, "chain": "evm:1" }],
  "total": 1,
  "page": 1,
  "page_size": 20
}

Get a payment

Returns a single payment by ID, scoped to your app. This is how you poll for status changes.

GET /api/v1/payments/<payment_id> API key

404: Payment not found.

curl http://localhost:8000/api/v1/payments/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "X-API-Key: pk_live_abc123..."
Returns 200
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payment_token": "k7vP...random9xU",
  "status": "confirmed",
  "amount_fiat": 1000,
  "currency": "USD",
  "chain": "evm:1",
  "stablecoin": "USDC",
  "deposit_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
  "tx_hash": "0xabc...",
  "amount_received": 1000,
  "confirmations_seen": 12,
  "created_at": "2026-01-15T10:30:00Z",
  "confirmed_at": "2026-01-15T10:35:00Z",
  "completed_at": "2026-01-15T10:35:00Z",
  "swept_at": "2026-01-15T10:40:00Z",
  "expires_at": null,
  "payment_url": "https://pay.yourdomain.com/pay/k7vP...random9xU/"
}

Public payment view (no auth)

A limited payment view for customer-facing pages. Looked up by <code>payment_token</code> and requires no API key.

GET /api/v1/public/payments/<payment_token> Public

404: Payment not found.

curl http://localhost:8000/api/v1/public/payments/k7vP...random9xU
Returns 200
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payment_token": "k7vP...random9xU",
  "status": "confirmed",
  "amount_fiat": 1000,
  "currency": "USD",
  "chain": "evm:1",
  "stablecoin": "USDC",
  "deposit_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
  "tx_hash": "0xabc...",
  "amount_received": 1000,
  "created_at": "2026-01-15T10:30:00Z",
  "expires_at": null,
  "payment_url": "https://pay.yourdomain.com/pay/k7vP...random9xU/"
}

Payment network options (no auth)

Lists the (chain, token) pairs your customer may pay with for this payment. Sandbox payments only list sandbox test networks.

GET /api/v1/public/payments/<payment_token>/options Public

404: Payment not found.

curl http://localhost:8000/api/v1/public/payments/k7vP...random9xU/options
Returns 200
{
  "mode": "live",
  "locked": false,
  "selected": null,
  "options": [
    { "chain": "evm:8453", "chain_name": "base", "token": "USDC" },
    { "chain": "evm:137", "chain_name": "polygon", "token": "USDC" }
  ]
}

Select network + token (no auth)

Selects the network and token your customer will pay with and returns the deposit address to send to. Idempotent per network; the token may be changed until funds are detected, after which the destination is locked and further selections return <code>409</code>.

POST /api/v1/public/payments/<payment_token>/deposit Public

Request fields

chain string required

Format evm:<chainId> or svm:<chainId>.

token string required

Token symbol, e.g. USDC, USDT.

404: Payment not found. 409: Payment is already funded or terminal. 422: Network or token unavailable.

curl -X POST http://localhost:8000/api/v1/public/payments/k7vP...random9xU/deposit \
  -H "Content-Type: application/json" \
  -d '{"chain": "evm:8453", "token": "USDC"}'
Returns 201
{
  "chain": "evm:8453",
  "chain_name": "base",
  "token": "USDC",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38"
}

Hosted checkout page (no auth)

A platform-hosted checkout page for your customer. Shows the deposit address and QR code, polls for status, and on success redirects the customer to your App's <code>redirect_link</code>. The <code>payment_url</code> returned when creating a payment points here.

GET /pay/<payment_token>/ Public

404: Payment not found. Terminal statuses other than success render an inline error page.

# Open the hosted checkout page in a browser
open http://localhost:8000/pay/k7vP...random9xU/

Cancel a payment

Cancels a pending payment. Once cancelled it cannot be processed further and no deposit should be accepted. Cancellation is idempotent for the same payment.

POST /api/v1/payments/<payment_id>/cancel API key

404: Payment not found. 409: Payment cannot be cancelled in its current state.

curl -X POST http://localhost:8000/api/v1/payments/a1b2c3d4-e5f6-7890-abcd-ef1234567890/cancel \
  -H "X-API-Key: pk_live_abc123..."
Returns 200
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payment_token": "k7vP...random9xU",
  "status": "cancelled",
  "amount_fiat": 1000,
  "currency": "USD",
  "chain": "evm:1",
  "stablecoin": "USDC",
  "deposit_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
  "tx_hash": null,
  "amount_received": null,
  "created_at": "2026-01-15T10:30:00Z",
  "expires_at": null,
  "payment_url": "https://pay.yourdomain.com/pay/k7vP...random9xU/"
}

Supported chains

Sandbox keys

  • Sepolia (EVM) evm:11155111
  • BNB Smart Chain Testnet (EVM) evm:97
  • Solana Devnet (SVM) svm:103

Live keys

  • Ethereum (EVM) evm:1
  • Polygon (EVM) evm:137
  • Base (EVM) evm:8453
  • Arbitrum (EVM) evm:42161
  • Solana (SVM) svm:101

Status lifecycle

Payment statuses

pending detected confirming confirmed underpaid overpaid expired cancelled swept deposited_to_spusd sweep_failed

Errors

Status Meaning
401Not authenticated / invalid key
404Resource not found
409Conflict (state transition not allowed)
422Validation error (field-level)
429Rate limit (60 req/min per IP)
{
  "amount_fiat": ["Ensure this value is greater than or equal to 1."],
  "chain": ["Chain must be in format 'evm:<chainId>' or 'svm:<chainId>'"]
}
">