Paying with x402

Three steps: ask, sign, retry. No account is created at any point, and nothing is held on your behalf between calls.

1. Ask, and get a price

Call the endpoint normally. If your request is malformed you get a 400 — you are never quoted a price for a request that could not have succeeded. Otherwise you get a 402 carrying the challenge:

HTTP/1.1 402 Payment Required
content-type: application/json
www-authenticate: x402 realm="business-invoice-calculate", scheme="exact", network="eip155:196"

{
  "status": "payment_required",
  "x402Version": 2,
  "service_id": "business-invoice-calculate",
  "price_usd": "0.010",
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:196",
      "amount": "<atomic units of the asset>",
      "maxAmountRequired": "<same value, both spellings are emitted>",
      "asset": "0x… settlement token contract",
      "assetSymbol": "USDG",
      "decimals": 6,
      "payTo": "0x… the provider's receiving address",
      "resource": "https://api.dana-edu.pp.ua/v1/business/invoice-calculate",
      "maxTimeoutSeconds": 300
    }
  ]
}

Both amount and maxAmountRequired are present with the same value. Two official OKX pages disagree about which spelling is current, so both are emitted — a client reading either gets a correct challenge instead of a failed payment.

2. Sign an authorisation

Sign an EIP-3009 transferWithAuthorization for exactly the amount, asset and recipient named in the challenge. Base64-encode this payload and send it in the X-PAYMENT header:

{
  "x402Version": 2,
  "accepted": {
    "scheme": "exact",
    "network": "eip155:196",
    "amount": "<the amount from the challenge, unchanged>",
    "asset": "<the asset from the challenge>",
    "payTo": "<the payTo from the challenge>",
    "maxTimeoutSeconds": 300
  },
  "payload": {
    "signature": "0x… your EIP-3009 signature",
    "authorization": {
      "from": "0x… your wallet",
      "to": "<the payTo from the challenge>",
      "value": "<the amount from the challenge>",
      "validAfter": "0",
      "validBefore": "<unix seconds>",
      "nonce": "0x… 32 random bytes, never reused"
    }
  }
}

3. Retry

curl -i -X POST "https://api.dana-edu.pp.ua/v1/business/invoice-calculate" \
  -H "content-type: application/json" \
  -H "X-PAYMENT: <base64 of the payload above>" \
  -d '{ ... }'

On success you get 200 with the result and an X-PAYMENT-RESPONSE header containing the settlement receipt.

What gets checked, and why

If You get Why it is refused
The same authorisation is presented twice PAYMENT_REPLAY_DETECTED An authorisation is a bearer instrument until spent. If one could be replayed, every endpoint would be priced at "pay once, call forever". Sign a new one with a fresh nonce.
The amount differs from the challenge PAYMENT_WRONG_AMOUNT Refused in both directions. An amount that does not match means you and the provider disagree about the price, and guessing which is right is worse than saying so.
A different recipient PAYMENT_WRONG_RECIPIENT Compared case-insensitively, so EIP-55 checksum casing never causes a false rejection.
A different chain or token PAYMENT_WRONG_NETWORK / PAYMENT_WRONG_ASSET Atomic units are meaningless without the token they belong to. 3000 units of a 6-decimal stablecoin is $0.003; 3000 units of an 18-decimal token is dust.
validBefore has passed PAYMENT_EXPIRED Sign a new authorisation.
An unsupported scheme PAYMENT_UNSUPPORTED_SCHEME The error lists the schemes that would have worked for that endpoint.

Order of operations

Input is validated before payment is demanded, so a malformed request is never billed. Settlement happens after the work succeeds, so a request that errors is not charged for. Both are deliberate and both are covered by tests.

Batched settlement

Some sub-cent endpoints also accept the aggr_deferred scheme. A per-call on-chain settlement costs more than a $0.003 call earns, so batching is the only way those endpoints make sense — but it is advertised only where the broker actually supports it, because offering a scheme that will then be rejected is worse than not offering it. Each endpoint's page lists the schemes it accepts.