Pay for AuthPI API usage on-chain with the x402 standard — no Stripe, no monthly invoice, just sign and ship.
Last updated 2026-06-11
x402 is an HTTP-native payment standard that lets clients pay for individual API requests with cryptocurrency. AuthPI uses x402 as a self-service funding rail for the account credit ledger — instead of putting a credit card on file or signing a contract, you (or an autonomous agent acting on your behalf) sign on-chain payments that AuthPI converts into prepaid credits.
This page explains how x402 works inside AuthPI: when you’d choose it, what payment methods are, how the 402 challenge flow works, and how to integrate it into a client.
x402’s reference applications are discrete-resource services — each URL has a price, each request is paid for atomically, the server-caller relationship is anonymous. AuthPI is the inverse: identity infrastructure persists between requests, request graphs are correlated (an OAuth login is dozens of HTTP calls serving one logical flow), and billable units don’t map cleanly onto individual responses (creating a user costs money once but the user then exists “free” forever).
So AuthPI uses x402 deliberately differently:
What we preserve from x402’s spirit: the agentic property. Autonomous callers can sign up, fund themselves, and recover from depletion without a human-in-the-loop, no credit card form, no KYC. That’s the half of x402’s value proposition that’s actually unique — and we keep all of it.
Every AuthPI account has three relevant fields:
payment_methods[] — the list of methods that can fund this account. Each entry has its own id, enabled state, and lifecycle (created_at, optional disabled_at, optional removed_at). Methods are stored in the AccountDO’s SQLite, queryable atomically alongside the credit ledger.billing_mode — either "ungated" (writes proceed even on negative balance, billing happens out of band) or "gated" (writes require prepaid credits). Derived, not stored. It tracks the count of active payment methods: zero active methods → "ungated", one or more → "gated". There is no public endpoint to write this field — adding or removing methods is the customer-facing way to flip it. AuthPI staff can pin the value via the internal setGatingOverride RPC for cases like grandfathered post-paid customers; the override survives method changes until cleared.credit_balance_μusd — the running balance of the credit ledger. Source-agnostic: an x402 settlement and a Stripe charge add to the same balance.The model is intentionally non-exclusive. An account can have both an x402 method and a Stripe method enabled, and credits from either feed the same balance. Whether the account requires prepayment is determined by the derivation above (or the operator override).
created → enabled (active)
↔ disabled_at set → 15s grace: in-flight settlements still complete
│ └ re-enable: PATCH { "enabled": true } clears disabled_at
├ past grace → settlements refused, record retained for audit
└ removed_at set → TERMINAL: settlements refused immediately
(slot is freed for a replacement; entry kept for audit)
enabled and disabled are reversible — toggling enabled via PATCH /v1/accounts/:id/payment-methods/:pm_id flips between them and (when re-enabling) clears the disabled_at timestamp. removed_at is one-way: once set, the method cannot be reactivated and any retry of an in-flight settlement will be refused with a 409.
The 15-second grace period on disable handles the small race window where an admin disables a method just as a customer’s signed payment is mid-settlement. Anything signed before disabled_at + 15s settles successfully even if it lands during the grace window.
Security note: disable is for benign rotation, not compromise. The grace window honors any settlement signed before disable+15s, regardless of when the signature was authorized. If an attacker has captured a payment signature (e.g., via a leaked wallet log), they can replay it during the grace window even after you disable the method. To revoke a compromised method, use DELETE /v1/accounts/:id/payment-methods/:pm_id instead — the terminal removed_at state bypasses grace entirely and refuses all subsequent settlements immediately. Disable is a clean-rotation tool; remove is the security-revocation tool.
x402 is designed for callers who want agentic, programmatic, or one-off access to AuthPI without a billing relationship:
For high-volume production workloads with predictable usage, traditional billing is usually simpler. x402 shines when “just charge me for what I use, pay me as I go” is the operating model.
Before any x402 settlement can happen, the account needs an active x402 payment method. The account’s billing_mode is derived from the count of active methods, so adding the first one flips the derived value from "ungated" to "gated" — write operations from this point on require prepaid credits, and the inline-settle path described below becomes active. Removing the last active method flips the derivation back. Operators with a need to pin the value (force-ungated for a grandfathered customer, force-gated during a payment dispute) use the internal setGatingOverride RPC; that override wins over the derivation until explicitly cleared.
This endpoint is x402-only. Stripe billing is operator-provisioned through a Setup Intent flow (not yet exposed); attempting to POST {"type": "stripe", ...} here returns a 400.
POST /v1/accounts/:account_id/payment-methods
Authorization: Bearer <api-key> # requires manage:accounts scope
Content-Type: application/json
{
"type": "x402",
"label": "Team wallet",
"auto_topup_increment_μusd": 1000000 // $1 minimum; tune higher for larger buffers
}
The response includes the new method’s id (pm_xxxxxxxx...). You can also restrict which wallets are allowed to fund the account by setting allowed_payer_wallets: ["0x..."] — useful for security-conscious accounts that want to lock topups to a known wallet. Most accounts leave it unset (matches x402 spec norms — anyone can pay). The list of methods on the account (active, disabled, and removed) is read inline from GET /v1/accounts/:id under payment_methods; there’s no separate list endpoint.
Disable a method (e.g., during a wallet rotation): PATCH /v1/accounts/:id/payment-methods/:pm_id with {"enabled": false}. The method enters the 15-second grace window before refusing new settlements.
Remove permanently: DELETE /v1/accounts/:id/payment-methods/:pm_id. The method gets a removed_at timestamp but stays in the list for audit; the slot becomes free for a replacement.
This is what happens when a gated account on x402 makes a write request without enough credit.
POST /v1/accounts/:account_id/issuers
Authorization: Bearer <api-key>
The credits middleware tries to reserve the operation cost. If the balance is short, AuthPI looks up the account’s active x402 method, computes a batched challenge amount (max(operation_cost, auto_topup_increment_μusd, $1)), and replies with HTTP 402:
{
"error": "insufficient_credits",
"error_description": "Payment required to perform this operation",
"operation": "issuers.create",
"cost_μusd": 1000000,
"retryable": false
}
The body shows cost_μusd: 1000000 ($1) even though issuers.create only costs $0.10 — the challenge asks for a buffer that’ll cover this op plus ~200 future ones. Standard x402 challenge headers (accepted schemes, payTo, network) accompany the body.
The client’s x402 library reads the challenge, builds a payment payload (the exact scheme on EVM chains using EIP-3009 signed transfers), and signs with the wallet that’s funding the account.
POST /v1/accounts/:account_id/issuers
Authorization: Bearer <api-key>
PAYMENT-SIGNATURE: <base64-payload>
AuthPI verifies the signature with an x402 facilitator (Coinbase), waits for on-chain settlement, and writes a topup ledger entry referencing the on-chain tx (x402:eip155:8453:0x...). The full $1 lands in the credit balance.
The middleware then deducts only the operation cost ($0.10) for the current write — the remaining ~$0.90 stays in the buffer. The handler runs against that fresh balance and returns the normal 200 response with the issuer object. A PAYMENT-RESPONSE header carries the settlement receipt.
The next ~200 gated operations from the same account hit a healthy balance and return immediately. No 402, no signing, no settlement — just normal API responses. The amortization is the win.
If you want to pre-fund the account in a discrete operation rather than wait for the credit-low path:
POST /v1/accounts/:account_id/credits/topups
Authorization: Bearer <api-key> # requires manage:accounts scope
PAYMENT-SIGNATURE: <base64-payload>
Content-Type: application/json
{ "amount_μusd": 10000000 } # $10
Use this when:
auto_topup_increment_μusdThe endpoint accepts up to $100 per call. For larger funding rounds, call it multiple times. The response carries the same PAYMENT-RESPONSE settlement receipt header as inline settlement, plus a JSON body with the new balance, the ledger entry id, and the on-chain payment reference.
If the account has no active x402 payment method, the endpoint returns 404 payment_method_not_found before attempting any settlement — your wallet is never charged on a misconfigured account.
x402 payments are real money. Three layers of idempotency apply:
topup entry’s reference field is x402:<network>:<tx-hash>; a duplicate tx hash returns the original ledger entry instead of double-crediting.Idempotency-Key replay for retried POST requests. AuthPI caches the response (status, body, AND the PAYMENT-RESPONSE header) for 24 hours. A retry with the same key returns the cached response without re-running anything.You can safely retry a top-up after a network blip without worrying about double-charging or losing the receipt.
When a gated write triggers an inline settlement and the actual handler later fails (validation error, missing parent resource, etc.), the ledger ends up:
+ topup (irreversible — on-chain payment was real)
- usage (reservation, debited at the start of the write)
+ refund (reversal of the usage entry, automatic on handler failure)
─────────────────────
net balance: still + topup
The customer’s payment funded a buffer; the failed write doesn’t take the funded money away. Your next request runs against that balance directly — no second on-chain payment needed.
| Status | Error code | Meaning |
|---|---|---|
| 402 | insufficient_credits | Account is gated and out of credit; here’s the challenge to top up. Not retryable until a payment is signed. |
| 402 | payment_settlement_failed | Client sent a signature but the on-chain transfer was rejected (insufficient wallet funds, gas issue). Retryable with a different signature. |
| 402 | payer_not_allowed | Method has an allowed_payer_wallets allowlist and the signed payment came from a wallet not in it. Wallet is not charged. Either sign from an allowlisted wallet or have an admin update the method’s allowlist. |
| 404 | payment_method_not_found | Account has no active x402 method. Add one via POST /v1/accounts/:id/payment-methods. |
| 409 | payment_method_revoked_during_settlement | The payment method was removed (or its allowlist tightened to exclude this payer) between the on-chain settlement and the credit write. The payment is real and recorded on-chain — the response includes payment_reference for manual reconciliation. Do not retry; contact support with the payment_reference. |
| 502 | x402_facilitator_unavailable | AuthPI couldn’t reach the x402 facilitator. Retryable after a backoff. |
Billing posture and current credit state live inline on the Account object — there’s no dedicated /credits endpoint.
GET /v1/accounts/:account_id
{
"data": {
"id": "acc_...",
"billing_mode": "gated",
"balance_μusd": 5000000,
"credits_run_out": false,
"payment_methods": [
{
"id": "pm_...",
"type": "x402",
"enabled": true,
"auto_topup_increment_μusd": 1000000,
"created_at": 1714000000000
}
],
"...other account fields..."
}
}
credits_run_out is a sticky flag that flips to true the first time a write hits zero balance and resets to false on the next successful top-up that lifts the balance back above zero. Useful for triggering “low balance” alerts without polling for trends.
payment_methods includes active, disabled, and soft-removed entries — clients filter on removed_at/enabled/disabled_at for the view they need (e.g., a settings UI shows non-removed methods; an audit log shows everything).
For the full ledger history:
GET /v1/accounts/:account_id/credits/ledger?limit=50
Returns ledger entries in reverse chronological order with cursor-based pagination. Use the reference field on topup entries to cross-reference your wallet’s transaction history.
A minimal integration using a hypothetical x402 client library to top up $1 of credits:
import { X402Client } from "@x402/client";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
// 1. Add an x402 payment method to the account (one-time setup)
await fetch(`https://api.authpi.com/v1/accounts/${accountId}/payment-methods`, {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "x402",
label: "Team wallet",
auto_topup_increment_μusd: 1_000_000, // $1 buffer per topup
}),
});
// 2. Set up a wallet for funding
const wallet = createWalletClient({
account: privateKeyToAccount(process.env.X402_WALLET_PRIVATE_KEY!),
transport: http(),
});
const x402 = new X402Client({ wallet });
// 3. Top up explicitly (or just let inline settle handle it on the next gated write)
const challenge = await fetch(
`https://api.authpi.com/v1/accounts/${accountId}/credits/topups`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ amount_μusd: 1_000_000 }),
},
);
if (challenge.status !== 402) {
return await challenge.json(); // succeeded immediately or hit a different error
}
// 4. Sign the payment from the challenge headers and replay
const paymentHeader = await x402.signFromChallenge(challenge);
const settled = await fetch(
`https://api.authpi.com/v1/accounts/${accountId}/credits/topups`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"PAYMENT-SIGNATURE": paymentHeader,
"Idempotency-Key": crypto.randomUUID(), // safe across network blips
},
body: JSON.stringify({ amount_μusd: 1_000_000 }),
},
);
const result = await settled.json();
// result.data.balance_μusd is the new running balance
// result.data.payment_reference is x402:eip155:8453:0x... — your on-chain receipt
The same pattern applies to any gated write — for inline settlement you’d be calling, say, POST /v1/accounts/:id/issuers instead of the top-up endpoint, and the response body would be the issuer object rather than a balance update.
To accept x402 payments, an AuthPI deployment needs three things configured per environment:
X402_PAY_TO — the receiving wallet address (set as a Worker secret, not a plain var)X402_NETWORK — the CAIP-2 network identifier (e.g. eip155:8453 for Base mainnet, eip155:84532 for Base Sepolia)X402_FACILITATOR_URL — optional override for the x402 facilitator; defaults to the SDK’s built-in (Coinbase) facilitatorThe receiving wallet is per-environment, not per-account. Per-account wallets would create significant treasury complexity (HD derivation, sweeps, gas, compliance fan-out) for marginal forensic value, since request-level attribution is already 1:1 via the on-chain tx hash stored as the ledger entry’s reference.