> Markdown version of https://authpi.com/docs/concepts/credits-and-billing/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# Credits & Billing

Pay for AuthPI API usage on-chain with the x402 standard — no Stripe, no monthly invoice, just sign and ship.

**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.

## How AuthPI uses x402 (and how it differs from the spec's typical examples)

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:

- **As a funding rail, not a billing model.** The 402 challenge means "you're out of credit, here's how to top up" — not "this resource costs $X." Settled payments fund a shared credit balance; the actual API operations bill against that balance from a separate ledger.
- **Batched, not per-request.** A single signed payment ($1 by default) covers ~200 typical operations, amortizing on-chain costs. The agent signs once and gets a buffer.
- **As one of multiple payment methods.** An AuthPI account holds a list of payment methods (x402, Stripe). Adding x402 is administrative; settlements feed into the same ledger as any other source.

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.

## Account billing model

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).

### Method lifecycle

```
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.

## When to use x402

x402 is designed for callers who want **agentic, programmatic, or one-off** access to AuthPI without a billing relationship:

- An autonomous agent provisioning identities for a workflow it's running
- A team that wants every environment (dev, staging, prod) on a separate wallet for clean cost attribution
- Cases where API calls are infrequent enough that a Stripe subscription is overkill
- Any caller that prefers signing on-chain over a credit-card form

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.

## Setting up an x402 payment method

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.

## The 402 challenge / settlement flow

This is what happens when a gated account on x402 makes a write request without enough credit.

### Step 1 — The bare request

```
POST /v1/accounts/:account_id/issuers
Authorization: Bearer <api-key>
```

### Step 2 — The 402 challenge

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**:

```json
{
  "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.

### Step 3 — The agent signs

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.

### Step 4 — Replay with PAYMENT-SIGNATURE

```
POST /v1/accounts/:account_id/issuers
Authorization: Bearer <api-key>
PAYMENT-SIGNATURE: <base64-payload>
```

### Step 5 — Settle and credit

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.

### Step 6 — Reserve and execute

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.

### Step 7 — Subsequent requests

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.

## Explicit top-up endpoint

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:

- You're onboarding and want to preload credits before any usage
- You want a larger buffer than the per-method `auto_topup_increment_μusd`
- You want clean separation in your accounting between top-ups and operational charges

The 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.

## Idempotency and receipts

x402 payments are real money. Three layers of idempotency apply:

- **Payment-reference uniqueness** at the database level. A `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.
- **On-chain nonce enforcement** at the EIP-3009 layer. The same signature can't settle twice on-chain.

You can safely retry a top-up after a network blip without worrying about double-charging or losing the receipt.

## Failed handlers and refund semantics

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.

## Error responses

| 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. |

## Reading credit state

Billing posture and current credit state live inline on the **Account object** — there's no dedicated `/credits` endpoint.

```
GET /v1/accounts/:account_id
```

```json
{
  "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.

## End-to-end example

A minimal integration using a hypothetical x402 client library to top up $1 of credits:

```typescript
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.

## Configuration

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) facilitator

The 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`.

## Related reading

- [Architecture](/docs/concepts/architecture) — how AuthPI's identity mesh fits together at large
- [Issuers](/docs/concepts/issuers) — the resource type whose creation is the most common gated write
- [Core API Reference](/docs/reference/core-api/) — full schema for the credits and payment-methods endpoints
- [x402 specification](https://www.x402.org/) — the underlying standard and supported schemes