API guides

Pay by agent

An invoice anyone can pay in USDC on Base — the x402 challenge, the signed authorization, the receipt.

Pay by agent — coming

The pay family is described in the contract and is not switched on here yet. Both endpoints answer not_enabled until it is. We are not giving a date; when it answers, this page says so on its own — it reads the API’s enablement, the same answer the API’s own gate gives.

What it is

An invoice sent by a payee who opted in carries a pay reference — on the PDF (“Pay by agent”), on the payer’s GET /v1/documents/{id} as pay_url, and as pay_url on the payer’s webhook deliveries of it. A delivery’s pay_url is null on the send’s own delivery (the reference is issued just after it) and present from the next event on; the payee’s copy never carries it. The reference is the capability: it lets its holder pay that one invoice and nothing else. No account, no API key. The protocol is x402 version 2: the server answers 402 with the terms, the payer’s wallet signs an EIP-3009 TransferWithAuthorization for exactly that amount, and Coinbase’s facilitator submits the transfer payer → payee on-chain.

Rivet is never in the flow of funds. The payee receives at an address Rivet Money has attested (one it can read on the payee’s own Coinbase credential — never a typed address); the facilitator moves the USDC from the payer’s wallet to that address; Rivet books the fact and the invoice reads paid. Nothing is custodied, netted or routed through Rivet at any point.

1. Read the challenge

GET /v1/pay/{payRef} is unauthenticated and rate-limited per IP (30 a minute by default; 429 carries Retry-After). A payable invoice answers 402 with the x402 header and a body that mirrors it for humans.

The challenge
From the live spec
curl -i "https://api.rivet.network/v1/pay/8mYv4sB2kQ9xL0pT3wZr6cHn1jFd5aUe7oGi2bKs0Vg"
PAYMENT-REQUIRED, decoded
From the published contract
{
  "x402Version": 2,
  "resource": {
    "url": "https://api.rivet.network/v1/pay/8mYv4sB2kQ9xL0pT3wZr6cHn1jFd5aUe7oGi2bKs0Vg",
    "description": "INV-0042 from Northwind Freight",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "1250000000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x9f2c…e41b",
      "maxTimeoutSeconds": 300,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ]
}
PaymentRequirements — one accepts[] entry
FieldTypeNotes
scheme*stringexact
network*stringCAIP-2 network id — `eip155:8453` (Base) or `eip155:84532` (Base Sepolia).
amount*stringThe debt in ATOMIC units of `asset` (USDC has 6 decimals: balance due × 10^6).
asset*stringThe USDC contract address on `network` (Circle's published address).
payTo*stringThe payee's receiving address — attested by Rivet Money, never typed.
maxTimeoutSeconds*integerThe validity window the wallet should sign for (300).
extraobjectThe token's EIP-712 domain (`name`, `version`) — differs per network (Base `USD Coin`, Base Sepolia `USDC`; both version `2`).
The 402 body
From the published contract
{
  "x402Version": 2,
  "code": "payment_required",
  "invoice": {
    "id": "6a5eed00000000000000d001",
    "number": "INV-0042",
    "payee": "Northwind Freight",
    "amount": 1250,
    "currency": "USD",
    "due_at": "2026-10-31T00:00:00.000Z"
  },
  "accepts": [ { "scheme": "exact", "network": "eip155:8453", "amount": "1250000000", "…": "…" } ]
}
  • Amount is atomic. accepts[0].amount is the balance due in USDC’s six decimals: $1,250.00 is 1250000000. The body’s invoice.amount is the same debt in fiat, for a person.
  • Sign the token’s domain, not a constant. extra carries the EIP-712 domain the USDC contract on that network expects — Base is USD Coin version 2, Base Sepolia is USDC version 2. Use what the challenge says.
  • A paid invoice answers 200 {status: "paid"}, never a second challenge. An unknown or rotated reference, or a payee that opted out, is 404 — nothing about the invoice is acknowledged.

2. Sign and settle

Sign TransferWithAuthorization with from your address, to the challenge’s payTo, value its amount, a 32-byte random nonce and a validBefore inside maxTimeoutSeconds. Wrap it as a PaymentPayload, base64 it, and POST it back as PAYMENT-SIGNATURE — or send the same object as the body’s payment if you cannot set headers.

PaymentPayload (before base64)
From the published contract
{
  "x402Version": 2,
  "accepted": { "scheme": "exact", "network": "eip155:8453", "amount": "1250000000", "asset": "0x8335…2913", "payTo": "0x9f2c…e41b", "maxTimeoutSeconds": 300, "extra": { "name": "USD Coin", "version": "2" } },
  "payload": {
    "signature": "0x<EIP-712 signature of TransferWithAuthorization>",
    "authorization": {
      "from": "0x<the payer's address>",
      "to": "0x9f2c…e41b",
      "value": "1250000000",
      "validAfter": "0",
      "validBefore": "1793491200",
      "nonce": "0x<32 random bytes>"
    }
  }
}
Settle
From the live spec
curl -X POST "https://api.rivet.network/v1/pay/8mYv4sB2kQ9xL0pT3wZr6cHn1jFd5aUe7oGi2bKs0Vg" \
  -H "PAYMENT-SIGNATURE: <base64 PaymentPayload>"
200 — the receipt
From the published contract
{
  "status": "paid",
  "document": { "id": "6a5eed00000000000000d001", "number": "INV-0042" },
  "transaction": "0x3fa1…c09d",
  "network": "eip155:8453"
}
PaymentReceipt
FieldTypeNotes
status*stringpaid
document*object
transaction*string | nullThe on-chain transaction hash.
network*string | nullCAIP-2 network id the transfer settled on.
  • PAYMENT-RESPONSE rides the 200: base64 {success, transaction, network, payer}, the x402 settlement response.
  • Idempotent. The same authorization sent twice answers 200 with the original transaction — the nonce was spent once.
  • The amount is checked again at settle. If the balance changed since the challenge, 409 payment_amount_stale carries details.current_amount; fetch a fresh challenge and sign that.

Refusals, by name

  • 409 approval_required — the payer’s own workflow holds the invoice on a pending approval. An outside payment never bypasses a company’s controls.
  • 409 financed_invoice_rivet_rail_only — a financed invoice repays from its settlement on the Rivet rail only.
  • 409 not_payable — a draft, a cancelled or disputed invoice, or one with a payment already scheduled through Rivet.
  • 402 with error set — the facilitator refused the signature (wrong domain, expired window, insufficient balance). Sign the re-issued challenge.
  • 502 x402_settle_failed — the facilitator could not settle after verifying. Nothing was booked; the invoice is unchanged; details.reason says why.
  • 400 — the x402 version 1 wire (X-PAYMENT) or a payload that is not version 2. This endpoint speaks version 2 only.

Paying with your own key

The payer organization’s key takes the same road: POST /v1/documents/{id}/pay with payment_method usdc answers the same 402, accepts the same PAYMENT-SIGNATURE, and applies the key’s payment_limits to the balance due first. A member’s agent and a stranger’s agent pay the same way.

The authenticated twin
From the live spec
curl -X POST "https://api.rivet.network/v1/documents/6a5eed00000000000000d001/pay" \
  -H "Authorization: Bearer rk_live_…" \
  -H "Idempotency-Key: pay-inv-0042-usdc" \
  -H "Content-Type: application/json" \
  -d '{"payment_method": "usdc"}'

Sandbox

A sandbox payee challenges on Base Sepolia (eip155:84532) with Circle’s testnet USDC and settles through the public testnet facilitator — fund a test wallet with Sepolia USDC and the whole loop runs end to end with no live funds. The live network is Base (eip155:8453).