Pay per request (x402)
No account, no key, no balance. An agent with a regular Ethereum-style wallet holding a little USDG and ETH pays for one request and gets the answer.
heura targets Robinhood Chain mainnet (chain id 4663) and takes payment in USDG. It is not launched and nothing has been audited. The API address is https://api.useheura.com/v1 and answers today, but only one seller (run by heura) is behind it so far, so do not send real funds until launch is announced.
USDG has no EIP-3009 transfer-with-authorization, so the stock x402 exact scheme and facilitators cannot be used. heura uses a single scheme, transfer-receipt: you broadcast a plain USDG transfer yourself and the server only reads the on-chain receipt. heura holds no key that can move a buyer's funds. sdk/pay.mjs is a working reference client.
Pay from an EOA (Phantom, MetaMask, Rabby, or a plain key). Smart-contract wallets are refused with unsupported_wallet_type. GET /x402/info shows whether pay per request is switched on.
The flow
- 1Ask
POST /x402/v1/chat/completionswith a normal chat body and no payment. You get 402, aPAYMENT-REQUIREDheader (base64 JSON) and the same JSON as the body. - 2Pay
Send one transaction on Robinhood Chain: USDG
transfer(payTo, amount)for at least the quoted amount. Appendextra.calldata_suffixto the calldata, i.e.0xa9059cbb+ payTo padded to 32 bytes + amount padded to 32 bytes + the suffix bytes. USDG ignores the trailing bytes; the server uses them to match the payment to your quote. - 3Collect
Wait for confirmations, then repeat the identical request with a
PAYMENT-SIGNATUREheader. You get the completion and aPAYMENT-RESPONSEheader.
The quote
{
"x402Version": 2,
"accepts": [{
"scheme": "transfer-receipt",
"network": "eip155:4663",
"asset": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
"payTo": "<payee address>",
"amount": "1000",
"maxTimeoutSeconds": 300,
"extra": { "memo": "<quote id>", "calldata_suffix": "0x...", "name": "USDG", "decimals": 6, "howTo": "..." }
}]
}The payment header
PAYMENT-SIGNATURE: base64( JSON {
"x402Version": 2,
"accepted": <accepts[0]>,
"payload": { "quoteId": "<extra.memo>", "txHash": "<0x transaction hash>" }
})Rules the server enforces
| Situation | Result |
|---|---|
| Paid less than quoted, even by 1 unit | 402 underpaid |
| Paid the right amount with a different reference | 402 memo_mismatch |
| Paid another address | 402 no_payment_to_payee |
| Paid in a look-alike token | 402 no_payment_to_payee |
| Paid from a smart-contract wallet | 402 unsupported_wallet_type |
| Paid, then changed the request body | 402 request_does_not_match_quote |
| Used the same payment twice (also across top-up and x402) | 409 quote_already_used / payment_already_used |
| Sent the same payment twice at once | exactly one is served, the other gets 409 |
| Payment not yet buried under enough blocks | 402 not_enough_confirmations (the server waits up to 15 s first; retry the same request) |
| Transaction older than 15 minutes | 402 transaction_too_old |
| Quote older than five minutes | 402 quote_expired |
| Upstream failed after you paid | 502, recorded as refund due |
What you pay
- The quote is the worst case: prompt size plus
max_tokens. Minimum $0.001. - The route accepts
max_tokensup to 1,024 and no streaming. - A quote is bound to the exact request, works once, and expires after five minutes. The server also waits for block confirmations before it serves.
Reference client
HEURA_URL=... PAYER_KEY_FILE=./payer.key MAX_USDG=0.05 \
node pay.mjs openai/gpt-6-luna "Hello"Set MAX_USDG so a wrong quote can never spend more than you intend. The client reads the quote, checks the token and network, sends the transfer from your key and replays the request.