API reference

Base URL is your deployment. Authenticate with Authorization: Bearer <api key>.

The key can create payments, so it belongs on your server only.

Create a payment

POST /api/v1/payments

FieldType
amountstringDecimal USDC, e.g. "25.00". Optional when line_items is given.
line_itemsarrayItemised breakdown. Derives amount when that is omitted.
descriptionstringShown at checkout. Max 500 characters.
referencestringYour own order id. Shown at checkout.
success_urlstringWhere the payer returns after paying. Must be https.
customer_emailstringShown at checkout. Never required.
customer_namestringShown at checkout.
invoice_numberstringTurns the payment into an invoice.
due_atstringISO 8601. Extends the payment window to this date.
metadataobjectReturned untouched in webhooks and on retrieval.

Send Idempotency-Key to make retries safe — the same key returns the original payment rather than creating a second one.

Line items

{
  "line_items": [
    { "description": "Pro plan (30 days)", "unit_amount": "20.00", "quantity": 1 },
    { "description": "Extra seat",         "unit_amount": "5.00",  "quantity": 2 }
  ]
}

The amount is derived — 30.00 here — so an itemisation can never disagree with the charge. You may also send amount as well, and a mismatch is rejected with both figures, which catches an integration bug before a customer sees it.

At most 50 items. Quantities are whole numbers.

Response

{
  "id": "0x7f3a...",
  "status": "pending",
  "amount": "25.00",
  "amount_received": null,
  "currency": "USDC",
  "pay_url": "https://your-deployment/pay/0x7f3a...",
  "deposit_address": "0xe374...",
  "fee_bps": 50,
  "expires_at": "2026-07-20T12:00:00.000Z",
  "created_at": "2026-07-20T11:00:00.000Z"
}

id is a 256-bit random value. It is safe to show a payer — it grants nothing except the ability to see and pay that one payment — but it must never be guessable, which is why it is not sequential.

Retrieve a payment

GET /api/v1/payments/{id}

Use this to reconcile when a webhook is late or was missed. Scoped to your account: another merchant's payment is a 404, not a disclosure.

Statuses

StatusMeaning
pendingCreated, nothing received
settlingFunds seen, settlement in flight
confirmedPaid in full and settled
underpaidSomething arrived, less than invoiced
overpaidMore than invoiced arrived
expiredWindow closed with nothing received
failedSettlement reverted; funds are recoverable

Only confirmed means you have been paid. Treat underpaid as unpaid until the remainder arrives. expired is not final — a late payment to a derived address still settles.

Webhook endpoints

POST /api/v1/webhook_endpoints with { "url": "https://..." } returns a signing secret, shown once.

GET /api/v1/webhook_endpoints lists them. DELETE /api/v1/webhook_endpoints/{id} removes one.

URLs must be https and publicly resolvable. Private, loopback and link-local addresses are rejected — see security.

Public endpoints

No authentication; the payment id is the capability.

GET /api/public/payments/{id}What the checkout renders from
GET /api/public/payments/{id}/qrDeposit address as SVG
POST /api/public/payments/{id}/syncConfirm from a transaction hash
GET /api/public/payments/{id}/cross-chainList source chains, or quote one with ?chain=
POST /api/public/payments/{id}/cross-chainRecord a burn so it gets redeemed on Arc

These send CORS headers so the widget can call them from your domain.

Errors

Standard codes with a JSON body:

{ "error": "line_items sum to 10.00 USDC but amount is 40.00 USDC" }
400Validation failed; the message says what
401Missing, unknown or revoked API key
404Not found, or not yours