← Docs

API

The dashboard is just one client of this API. Everything the UI can do, HTTP can do, and the other way round. All responses are JSON; BigInt values (blocks) and USDC amounts are sent as strings so precision is never lost.

Amounts are always in smallest USDC units (6 decimals), except when creating an invoice, where you send a plain number ("85.00") and the backend converts.

Merchants

MethodPathNotes
GET/api/merchantsAll merchants
POST/api/merchants{ name, walletAddress }, upsert by wallet

walletAddress is unique. Posting the same wallet again updates name instead of creating a second merchant.

Invoices

MethodPathNotes
GET/api/invoices?merchantId=&status=&chainId=List, newest first. All filters optional.
POST/api/invoicesCreate; 201 with paymentLink
GET/api/invoices/:idDetail, including merchant and payment (if any)
DELETE/api/invoices/:idSet CANCELLED if not PAID

Body for POST /api/invoices:

{
  "merchantId": "clx...",
  "chainId": 4663,
  "description": "Logo design, milestone 2",
  "customerName": "Alex",
  "amount": "85.00",
  "dueAt": "2026-10-01T00:00:00Z"
}

Validation (zod): chainId optional, must be one of the networks in GET /api/chains (defaults to the deployment's default chain), description 1 to 500 characters, amount matching ^\d+(\.\d{1,6})?$ and > 0, dueAt optional ISO datetime. Unknown merchant: 404.

The invoice object:

{
  "id": "clx...",
  "onchainId": "0x9f2a...",
  "chainId": 4663,
  "merchantId": "clx...",
  "customerName": "Alex",
  "description": "Logo design, milestone 2",
  "amount": "85000000",
  "status": "PENDING",
  "dueAt": null,
  "createdAt": "2026-09-17T08:12:00.000Z",
  "merchant": { "id": "...", "name": "Acme Studio", "walletAddress": "0x..." },
  "payment": null,
  "paymentLink": "https://.../pay/clx..."
}

status is one of PENDING, PAID, CANCELLED, EXPIRED (the last is reserved; nothing sets it yet).

Verification

MethodPathNotes
POST/api/invoices/:id/verify{ txHash }: check receipt and event, set PAID
CodeMeaning
200Matched; the invoice is now PAID (or already was; idempotent)
202Undecidable yet: tx not found yet or confirmations short. Retry.
400Tx reverted, no event for this invoice, or terms mismatch. Body has reason. Do not retry.

Chains

MethodPathNotes
GET/api/chainsNetworks this deployment accepts, with paymentProcessor and usdc per chain
POST/api/rpc/:chainIdSame-origin JSON-RPC relay used by the browser and the wallet (allow-listed methods, rate limited)
{
  "default": 4663,
  "chains": [
    { "chainId": 4663, "name": "Robinhood Chain", "testnet": false, "explorerUrl": "https://robinhoodchain.blockscout.com",
      "paymentProcessor": "0xD591A0d397179dE0692d50f43AC450C6cDF9C66D", "usdc": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", "rpc": "/api/rpc/4663" }
  ]
}

To pay an invoice without the UI, call pay(salt, merchant, amount) on the paymentProcessor of the invoice's chainId, where salt = keccak256(invoice.id).

Indexer

MethodPathNotes
POST/api/indexer?chainId=Scan PaymentReceived from the last block to the head on every enabled chain, or on one

Requires an x-indexer-secret header equal to INDEXER_SECRET. Without it, 401. Each chain keeps its own cursor; a chain whose RPC fails is reported in place without stopping the others (207). Response:

{ "ok": true, "chains": [
  { "chainId": 4663, "scanned": 1843, "applied": 2, "from": "18204311", "to": "18206153" }
] }

Safe to call as often as you like; already-applied events are skipped.

Export and summary

MethodPathNotes
GET/api/invoices/export?merchantId=CSV of the merchant's invoices, with network, chain_id, txHash, tx_url and paidAt for PAID ones
GET/api/stats?merchantId={ total, paid, pending, totalReceived, totalOutstanding }

totalReceived is summed from Payment records (the actual amounts received), totalOutstanding from PENDING invoices. Both are smallest-unit strings.

What is missing

There is no authentication on these endpoints. Anyone who can reach the server can create invoices for any merchant and read anyone's invoices. For production, add sign-in with wallet (SIWE) or put the API behind your own proxy. See Risks and limits.

Next: Risks and limits.