The BlockPay REST API.
All requests are JSON over HTTPS. Amounts are integer-string minor units (USDC has 6 decimals, so 4900000 is $4.90). Times are Unix seconds.
/api/invoicesCreate an invoice
Creates a fresh open invoice. Returns the canonical id, the on-chain commitment id and a checkout URL you can redirect the customer to.
Request
POST /api/invoicesContent-Type: application/json{ "merchantId": "merchant_acme", "merchantAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amount": "4900000", "currency": "USDC", "chainKey": "arc-testnet", "lineItems": [ { "label": "Pro Plan", "amount": "4900000" } ], "expiresAt": 1747353600}Response
HTTP/1.1 201 CreatedContent-Type: application/json{ "id": "inv_01HE2K6BX9C0", "onChainInvoiceId": "0x9b1c...e3f0", "checkoutUrl": "/checkout/inv_01HE2K6BX9C0", "invoice": { "id": "inv_01HE2K6BX9C0", "merchantId": "merchant_acme", "merchantAddress": "0xA0b8...eB48", "amount": "4900000", "currency": "USDC", "chainKey": "arc-testnet", "status": "open", "lineItems": [{ "label": "Pro Plan", "amount": "4900000" }], "createdAt": 1747350000, "expiresAt": 1747353600 }}Error codes
| Code | Meaning |
|---|---|
| 400 invalid_json | Body could not be parsed as JSON. |
| 400 invalid_field | A required field was missing or malformed. The response includes the failing field name. |
/api/invoices/{id}Fetch a single invoice
Returns the latest known state of one invoice, including settlement information once the on-chain transfer has confirmed.
Request
GET /api/invoices/inv_01HE2K6BX9C0Response
HTTP/1.1 200 OKContent-Type: application/json{ "invoice": { "id": "inv_01HE2K6BX9C0", "merchantId": "merchant_acme", "merchantAddress": "0xA0b8...eB48", "amount": "4900000", "currency": "USDC", "chainKey": "arc-testnet", "status": "paid", "settledTxHash": "0x4f1c...92a0", "settledAt": 1747350522 }}Error codes
| Code | Meaning |
|---|---|
| 404 not_found | No invoice exists with that id. |
/api/invoicesList invoices
Lists invoices, optionally filtered. Supports merchantId and status query parameters. Both are independent — pass either, both, or neither.
Request
GET /api/invoices?merchantId=merchant_acme&status=openResponse
HTTP/1.1 200 OKContent-Type: application/json{ "invoices": [ { "id": "inv_01HE2K6BX9C0", "status": "open", "amount": "4900000" }, { "id": "inv_01HE2K6BXAA", "status": "open", "amount": "1200000" } ]}Error codes
| Code | Meaning |
|---|---|
| 400 invalid_status | status must be one of draft, open, paid, expired, void. |
/api/paymentsList on-chain payments
Lists payments recorded by the BlockPay indexer. Filter by merchantId, chainKey, or both. Useful for reconciliation against your own ledger.
Request
GET /api/payments?merchantId=merchant_acme&chainKey=arc-testnetResponse
HTTP/1.1 200 OKContent-Type: application/json{ "payments": [ { "id": "pay_01HE2K7Z11", "invoiceId": "inv_01HE2K6BX9C0", "chainKey": "arc-testnet", "txHash": "0x4f1c...92a0", "amount": "4900000", "currency": "USDC", "from": "0xCustomer...", "to": "0xA0b8...eB48", "blockNumber": 21893444, "confirmedAt": 1747350522 } ]}Error codes
| Code | Meaning |
|---|---|
| 400 invalid_chain | chainKey is not a known chain. |
/api/webhooks/circleCircle webhook ingestion
Inbound endpoint for Circle's transfer webhooks. The request is HMAC-verified against the shared Circle webhook secret; invalid signatures are rejected with 401. You do not call this endpoint — Circle does.
Request
POST /api/webhooks/circleContent-Type: application/jsonX-Circle-Signature: t=1747350500,v1=<hex hmac sha256>{ "type": "transfers.created", "data": { "id": "ct_01HE2K7Z11", "amount": { "amount": "49.00", "currency": "USD" }, "destination": { "type": "blockchain", "address": "0xA0b8...eB48", "chain": "ARC" } }}Response
HTTP/1.1 200 OKContent-Type: application/json{ "ok": true }Error codes
| Code | Meaning |
|---|---|
| 401 invalid_signature | HMAC signature did not match. |
| 400 invalid_payload | Body could not be parsed or required Circle fields were missing. |