API Reference
Every endpoint below speaks JSON, lives under https://api.infraio.xyz
(prod) or https://api-dev.infraio.xyz (test), and authenticates via
HMAC-SHA256 — see Authentication
for the signing ritual and Errors for the
error envelope shape.
This page is the index. Each row links to the deepest existing write-up; if a row only references a path, the endpoint exists today but is documented inline in the relevant concept page rather than its own reference page.
Gateway path prefixes and their auth model:
/b2b/v1/*— HMAC-signed with your secret key (sk_…). The merchant backend surface./payment/v1/*— Bearer JWT (dashboard sessions). Used by the merchant dashboard frontend; not for third-party integrators./pub/v1/*— Bearer-of-truth in the path (anrfqt_…token for refund requests). No credentials. Safe to call from a browser./checkout/:key/*— Public prefix for the hosted checkout flow.keyis thecst_…session key returned at create time; the buyer’s browser is the only caller. No credentials.
Checkout
| Method | Path | Purpose | Notes |
|---|---|---|---|
POST | /b2b/v1/checkout-sessions/quick | Create a session in one call — order + checkout-session minted together. | See Quickstart for the request body and sample. |
POST | /b2b/v1/checkout-sessions | Create a session against an existing order. Use when your platform already has its own order model and you want one session per attempt. | The two-step flow. |
GET | /b2b/v1/checkout-sessions/by-order/{order_id} | List all sessions ever minted for an order. | Useful when a buyer abandoned a session and you want to surface the previous attempts in your dashboard. |
GET | /checkout/{session_key} | Public — the hosted checkout page fetches this. Buyer-facing fields only (no internal references). | No signing; takes the session_key as the bearer-of-truth. |
POST | /checkout/{session_key}/intent | Public — pick a payment method on the hosted page. Emits a PaymentIntent with the deposit address. | Called by checkout-web on user method selection. |
POST | /checkout/{session_key}/verify | Public — let the buyer paste a tx hash to short-circuit confirmation wait. | Falls through to chain watcher if hash is wrong. |
Orders
Orders are the timeless billable entity. A single order can back multiple checkout sessions (e.g. buyer abandons, retries).
| Method | Path | Purpose | Notes |
|---|---|---|---|
POST | /b2b/v1/orders | Create an order without a session. | Use when you want to send the buyer a payment link later instead of redirecting them immediately. |
GET | /b2b/v1/orders/{id} | Read a single order with line items + status. | Status: PENDING → PAID | PARTIAL_PAID | CANCELED. After refund: PARTIALLY_REFUNDED | REFUNDED. |
GET | /b2b/v1/orders/by-merchant/{merchant_id} | List your orders, cursor-paginated. | See Cursor pagination for the cursor protocol. |
PATCH | /b2b/v1/orders/{id}/cancel | Mark an unpaid order canceled. Emits order.canceled. | Fails if the order is already paid. |
PATCH | /b2b/v1/orders/{id}/reopen | Reverse an auto-cancel (canceled_reason=payment_timeout). | Useful if the buyer comes back after the TTL expired. |
Refunds
See Refunds concept page for the saga flow and token lifecycle.
Merchant-initiated
| Method | Path | Purpose | Notes |
|---|---|---|---|
POST | /b2b/v1/merchants/{merchant_id}/refunds | Merchant-initiated refund. Auto-approved (skips PENDING). | Emits payment.refund.approved immediately. |
Customer-initiated — refund-request tokens
The buyer fills the refund form on our hosted page; you only mint the token and deliver the URL. Two mint paths (HMAC for backends, JWT for the dashboard), three public token paths (read context, submit, request a renewal), and two dashboard-only paths for handling renewals.
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /b2b/v1/merchants/{merchant_id}/refund-requests | HMAC (sk_…) | Mint a token from your backend. Body: {ref_type, ref_value, amount, ttl_seconds?, metadata?, hide_summary?, hide_header?}. ref_type is one of order_id / order_number / session_id / session_key; ref_value is the matching identifier. amount is required and locks the maximum the buyer can submit. Default TTL 30 min. Emits refund_request.created (source: b2b). |
POST | /payment/v1/merchants/{merchant_id}/refund-requests | JWT (dashboard) | Mint a token from the merchant dashboard’s Issue Refund modal. Same body shape as the B2B variant. Default TTL 24 h. Emits refund_request.created (source: dashboard). |
GET | /pub/v1/refund-requests/{token} | Token in path | Public — checkout-web reads the form context (order summary, locked amount, current effective state). |
POST | /pub/v1/refund-requests/{token}/submit | Token in path | Public — buyer submits the form. Body: {reason, refund_to_address, amount?, metadata?}. amount is optional — when omitted, the merchant-locked link amount is used; when present, the server enforces amount ≤ locked amount. Creates the Refund row, emits payment.refund.requested, returns {link_token, refund_id} for the receipt page. |
POST | /pub/v1/refund-requests/{token}/request-renewal | Token in path | Public — buyer asks for a fresh link after expiry. Body: {customer_note?}. Emits refund_request.renewal_requested. |
GET | /payment/v1/merchants/{merchant_id}/refund-requests/renewals | JWT (dashboard) | List pending RENEWAL_REQUESTED tokens for the merchant’s renewal widget. Cursor-paginated. |
POST | /payment/v1/merchants/{merchant_id}/refund-requests/renewals/{token}/issue-new | JWT (dashboard) | Approve a renewal — mints a new ACTIVE token, retires the old one. Emits refund_request.renewed + refund_request.created (source: renewal). |
GET | /payment/v1/merchants/{merchant_id}/refund-requests/by-order/{order_id} | JWT (dashboard) | List every refund-request token ever minted against an order with effective state. Newest first. |
POST | /payment/v1/merchants/{merchant_id}/refund-requests/{token}/send-email | JWT (dashboard) | Queue an email delivery of the refund-request link to the customer. Body: {to}. Emits refund_request.email_send_requested. |
POST | /payment/v1/merchants/{merchant_id}/refund-requests/{token}/cancel | JWT (dashboard) | Merchant kill-switch — flips ACTIVE or RENEWAL_REQUESTED → CANCELED. Body: {reason?}. Idempotent: a second call after the status has already moved returns success without re-emitting. Emits refund_request.canceled on the first transition. |
Refund lifecycle (post-create)
Applies to both flows. The endpoints below operate on the Refund
row (id starts rfn_…), not the request token.
| Method | Path | Purpose | Notes |
|---|---|---|---|
GET | /b2b/v1/refunds/{id} | Read one refund. | Status: PENDING → APPROVED → EXECUTED | REJECTED. |
GET | /b2b/v1/refunds/by-merchant/{merchant_id} | List your refunds, cursor-paginated. | — |
POST | /b2b/v1/refunds/{id}/approve | Approve a PENDING refund (customer-initiated only — merchant-initiated lands APPROVED already). | Crypto: lands APPROVED, you call /submit-tx next. |
POST | /b2b/v1/refunds/{id}/reject | Deny a PENDING refund. | Emits payment.refund.rejected. |
POST | /b2b/v1/refunds/{id}/submit-tx | Crypto only — stamp the on-chain tx hash you broadcast. | Body: {tx_hash, network, token_address} — all three required. |
Catalog (read-only)
| Method | Path | Purpose |
|---|---|---|
GET | /v1/supported/networks | All chains InfraIO can settle on (mainnet + testnet, filtered by env). |
GET | /v1/supported/tokens | All stablecoins on those chains. |
GET | /v1/supported/currencies | Fiat currencies accepted for order.currency. |
GET | /v1/merchants/payment-methods | Methods THIS merchant has enabled — combine of platform catalog + per-merchant toggles. Used by checkout-web. |
GET | /v1/public/merchants/{merchant_id}/branding | Public — what the checkout page reads to skin itself. |
Health
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /health | None (public) | Plain liveness probe — returns {"status":"ok"}. This (no /v1 prefix) is the only unauthenticated health endpoint — point your k8s / uptime monitors here. |
GET | /payment/v1/merchants/{merchant_id}/health | Dashboard JWT | Per-merchant health view — recent intent settlement rate, sweep backlog. Useful for your own status pages. Requires a dashboard session token, not a B2B API key. Reachable only under the /payment/ gateway prefix — the bare /v1/... path is not publicly routed. |
GET | /payment/v1/stats/health | Dashboard JWT | Aggregate health across a merchant’s workspace tree. Not a public liveness probe — it sits behind the same JWT auth under the /payment/ gateway prefix. |
Cursor pagination
Every list endpoint accepts the same query params, returns the same
envelope. We use opaque cursors (base64url-encoded (created_at, id))
rather than offsets so a page never shifts when a row lands mid-scroll.
| Query param | Type | Default | Notes |
|---|---|---|---|
cursor | string | — | Opaque — copy the previous response’s next_cursor verbatim. |
limit | int | 20 | 1..100. |
sort_dir | 'asc' | 'desc' | desc | Sort by (created_at, id). |
from / to | RFC3339 | — | Optional time-window filter. |
search | string | — | Free-text filter where supported. |
Response envelope:
{
"orders": [ /* page rows */ ],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0...",
"has_next": true
}has_next is always present. next_cursor is omitted when has_next
is false. Don’t try to parse the cursor — its shape is internal and
will change.
What’s missing from this page
This index covers the merchant-facing surface — endpoints under
/admin/* (dashboard tools, KYB review, network management) and
internal gRPC routes are intentionally not listed. The OpenAPI spec
generated by swag covers the full surface; if you need it, ping
support and we’ll share a current snapshot.