> Markdown version of https://authpi.com/docs/guides/x402-agent-auth/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# Authenticate x402 agents with wallet verifiers

Bind x402-paying wallets to stable agent identities — add CAIP-2 wallet verifiers, resolve a payer wallet to an agent, and scope what it may do.

x402 is Coinbase's open payment protocol built on HTTP status code 402. When a client requests a paid resource, the server replies `402 Payment Required` with a machine-readable challenge; the client signs a payment from a crypto wallet (on EVM chains, an EIP-3009 signed transfer) and replays the request with a `PAYMENT-SIGNATURE` header. A facilitator verifies the signature and settles it on-chain. No account creation, no credit card form, no human in the loop.

That last property is why x402 is becoming the default rail for AI agents buying API access: an agent can discover your API, pay for a request, and consume the response entirely autonomously. But it ships with an identity gap.

## The identity gap

A verified payment signature tells you the money is good. It tells you nothing about *who* is calling:

- **A wallet is not an identity.** The same agent may pay from rotating wallets; one operator may fund many agents from one treasury wallet.
- **Payment is not permission.** "Paid $0.01" doesn't answer: which endpoints may this caller use? At what rate? On behalf of which of your customers?
- **You can't audit an address.** Logs full of `0x36f2...` are useless when a customer asks "what did *our* agent do last Tuesday?"

The fix is to give the paying wallet a stable identity: an AuthPI **agent** (`agt_` prefix) with a **wallet verifier** — a registered CAIP-2 wallet address that maps back to the agent, its scopes, and its organization memberships.

**Division of labor, plainly:** your x402 facilitator (e.g. Coinbase's) verifies payment signatures and settles them on-chain — AuthPI does not process payments and does not verify on-chain signatures. AuthPI stores and validates wallet verifiers, maintains the wallet → agent mapping, and gives agents scopes, org membership, OAuth tokens, and an audit trail. You combine the two: the facilitator answers "is the payment real?", AuthPI answers "who is paying, and what may they do?"

**Not to be confused with AuthPI's own billing.** AuthPI also *accepts* x402 payments — that's how customers fund their AuthPI account, documented in [Credits & Billing](/docs/concepts/credits-and-billing/). This page is the other direction: authenticating x402-paying agents that call **your** product.

## Step 1 — Create the agent

Agents live under an issuer. Create one with the Core API (API keys authenticate with HTTP Basic auth — key ID as username, secret as password):

```bash
curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "checkout-agent",
    "scopes": ["invoices:read", "orders:create"]
  }'
```

The response wraps the agent in `data` — note the `agt_` ID, which is the stable subject you'll see in logs and tokens:

```json
{
  "data": {
    "id": "agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f",
    "issuer_id": "i_8fK2mQp4xR7tWz",
    "name": "checkout-agent",
    "status": "active",
    "scopes": ["invoices:read", "orders:create"],
    "created_at": 1781222400000
  }
}
```

## Step 2 — Add a wallet verifier

Register the wallet the agent pays from. The body takes the address and the network as separate fields — `network` is a [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain ID (`eip155:8453` is Base mainnet), so together they form the CAIP-10 account `eip155:8453:0x36f2...`:

```bash
curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents/$AGENT_ID/verifiers" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "wallet",
    "name": "base-mainnet-treasury",
    "address": "0x36f2eAaB9e428DA1f4f24DDa75d2acD4cd9b7B17",
    "network": "eip155:8453"
  }'
```

```json
{
  "data": {
    "id": "v_5e8d2b7a91c3f49f2c1a7e4b3d48f6a0",
    "agent_id": "agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f",
    "type": "wallet",
    "status": "active",
    "name": "base-mainnet-treasury",
    "credential": { "address": "0x36f2eAaB9e428DA1f4f24DDa75d2acD4cd9b7B17", "network": "eip155:8453" },
    "created_at": 1781222400000
  }
}
```

Addresses are matched case-insensitively, and each `(network, address)` pair can only be registered once per agent. An agent holds up to 20 verifiers, so one agent can own wallets on several chains (Base mainnet and Sepolia, say) under a single identity.

## Step 3 — Resolve an incoming wallet to its agent

When a wallet verifier is added, AuthPI writes a reverse-lookup record to its edge KV store keyed `wallet:{network}:{address}` (address lowercased) pointing to `{ agent_id, issuer_id, verifier_id }`. Removing the verifier — or deleting the agent — removes the record, so the index never serves stale mappings.

Today you consume that mapping through the verifiers API and webhooks rather than a dedicated lookup endpoint: wallet verifiers expose their `address` and `network` in `GET .../agents/{agent_id}/verifiers` (secret verifiers never expose their hash), and the `agent.verifier.added` / `agent.verifier.removed` events tell you the moment a mapping changes. Build the same lowercased key on your side:

```typescript
// payer = the wallet that signed the x402 payment on this request,
// as reported by your facilitator's settlement response
const key = `${payer.network}:${payer.address.toLowerCase()}`; // "eip155:8453:0x36f2..."
const agentId = walletIndex.get(key); // your map, synced from the verifiers API + webhooks

if (!agentId) {
  return new Response("Payment accepted from unknown wallet — no agent registered", { status: 403 });
}
// agentId is a stable agt_ subject: rate-limit it, scope-check it, log it
```

The payment proves funds; the resolved `agt_` identity carries everything else — scopes, status (`suspend` an agent and act on it immediately, no wallet blacklisting), and the audit trail.

## Step 4 — Add a secret verifier for full OAuth tokens

Wallet resolution covers pay-per-request calls. When the agent needs a real access token — to call non-paid endpoints, or to carry scopes verifiable by any service — add a `secret` verifier and use the OAuth `client_credentials` grant:

```bash
curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents/$AGENT_ID/verifiers" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "type": "secret", "name": "cc-grant" }'
```

The response includes the plaintext `secret` **once** — store it now; only a SHA-256 hash is kept. The agent then mints tokens directly from your issuer's token endpoint:

```bash
curl -X POST "https://idp.authpi.com/$ISSUER_ID/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=$AGENT_ID" \
  -d "client_secret=$AGENT_SECRET" \
  -d "scope=invoices:read"
```

Agent tokens are deliberately short-lived — **5 minutes** — and carry a `dat` claim of `{"type": "agent"}` so your services can distinguish agent tokens from human identity tokens with a typed check instead of a `sub`-prefix heuristic. Requested scopes must be a subset of the agent's `scopes`; omit `scope` to get the full allowed set. The 5-minute TTL is the revocation primitive: suspend the agent and no new tokens mint, so a change propagates within one TTL window. The full flow is walked through in the [agents quickstart](/docs/quickstarts/agents/).

## Step 5 — Scope and limit the agent with org membership

If your product is multi-tenant, attach the agent to the customer's organization. The members endpoint accepts `agt_` IDs as `member_id`:

```bash
curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/organizations/$ORG_ID/members" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "member_id": "agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f",
    "scopes": ["billing:read"]
  }'
```

The membership is the tenancy record: the agent appears in the org's member list with `member_type: "agent"` and its own per-org scopes, and you can remove it to cut access to that tenant without touching the agent's wallets or secrets. One honest caveat: `client_credentials` tokens carry only the agent's own scopes — they don't embed org claims — so enforce per-org scopes server-side by checking the agent's membership via the members API.

## Next steps

- [Give an AI agent an identity in 10 minutes](/docs/quickstarts/agents/) — the secret-verifier and token flow, end to end
- [Agents](/docs/concepts/agents/) — the concept model: lifecycle, verifiers, scopes
- [Agents — Core API reference](/docs/reference/core-api/agents/) — every endpoint and field
- [Credits & Billing](/docs/concepts/credits-and-billing/) — the other x402: how accounts fund AuthPI itself