pahz documentation
How to open a tab, let an agent pay per call over x402, read the meter, and settle on Base. Sections 01–05 get you to a first paid call.
01Overview
pahz is a client-side payment layer for AI agents. An agent makes HTTP calls through the pahz fetch wrapper; when a service answers 402 Payment Required (x402), pahz checks the agent's tab — budget, rate cap, allowlist, price band — and either signs a USDC payment and retries, or holds the call and notifies the owner. Payments are batched into settlements on Base with verifiable receipts. Every payment and every hold is a line on the meter.
The agent never holds a key. The owner can close the tap at any time without stopping the agent.
02Vocabulary
| term | meaning |
|---|---|
| tab | A funded USDC account on Base with rules, assigned to one agent. |
| rule | budget, rate cap, allowlist, price band, expiry. |
| hold | A call pahz refused to pay for, pending owner action. |
| meter | The per-tab log of payments and holds. |
| settlement | A batch of payments written to Base as one transaction. |
| receipt | The settlement's on-chain hash covering its meter lines. |
| tap | The open/closed state of a tab. |
| signer | The pahz component that holds tab keys and signs authorizations. |
03Quickstart
import { pahz } from "@pahz/sdk";
const tab = await pahz.tab.open({
budget: 5, // USDC
rate: { perMin: 60 },
allow: ["search.api", "infer.llm", "tag:verified"],
band: { "infer.llm": 0.02, "*": 0.005 }
});
const fetch = pahz.fetch(tab); // drop-in
const r = await fetch("https://search.api/q?text=…"); // pays 0.002 if asked
console.log(await pahz.meter(tab).last(5));04Install
npm install @pahz/sdk # agent side
npm install @pahz/serve # service side (x402 helper)
Node 18+ or any runtime with fetch. The SDK talks to the pahz signer API and to Base via the RPC in PAHZ_RPC.
05Fund a tab
pahz.tab.open() deploys a tab contract on Base and returns its address. Send USDC to it from your wallet, or pass fundFrom to have the SDK request a signature. The tab is closed until funded; the first payment opens it.
06Tabs
A tab belongs to one owner and is assigned to one agent identity (an API key issued by the signer). Tabs are cheap; use one per agent. A tab's rules are set at open and changed only by the owner. The agent can read its own rules but not write them.
07Rules
| rule | enforced where | on violation |
|---|---|---|
| budget | tab contract + signer | hold; settlement reverts if exceeded |
| rate cap | signer | hold with retry-after |
| allowlist | tab contract + signer | hold; owner notified |
| price band | signer | hold; owner notified |
| expiry | tab contract | balance returns to owner |
08Holds
A hold returns a structured 402-held to the agent with the rule that held it and, where applicable, a retry-after. Holds appear in the owner's inbox with actions: allow once, allow always (edits the rule), raise band, ignore. Allowing once re-signs and retries the original call if the agent is still waiting.
09The meter
{ "tab": "0x41…", "n": 1832, "at": "2026-09-14T10:02:11Z",
"service": "search.api", "path": "/q", "price": 0.002,
"latency_ms": 41, "nonce": "…", "settlement": 77 }
{ "tab": "0x41…", "n": 1833, "held": "allowlist", "service": "newapi.io" }
Meter lines are append-only and exportable (JSON, CSV). Each paid line carries the settlement id it was included in.
10Settlements
Payments are signed as USDC transfer authorizations (EIP-3009 style) and batched. A settlement is triggered every 5 minutes or every 200 payments, whichever first, and moves USDC from the tab to each service's pay-to address in one transaction on Base.
11Receipts
Each settlement emits a receipt: settlement id, tab, total, count, and a Merkle root over its meter lines. Anyone with the meter export can verify a line was included; the owner can reconcile the meter to the chain to the cent.
12The tap
pahz tab close <id> flips the tab to closed with one owner signature. The signer stops signing immediately; the agent receives 402-held: tap_closed on every paid call and keeps running. open reverses it. Closing does not cancel pending settlement of already-signed payments.
13Service directory
Services can register their x402 endpoints with a name, tags and a published price schedule. Allowlists may reference tags (tag:verified, tag:search). Listing is optional; unlisted services can still be allowlisted by host.
14Architecture
agent ── pahz.fetch ──▶ service (x402)
│ 402 + price
▼
pahz signer ── rules (rate, band) ── sign auth ──▶ retry ──▶ 200
│ meter line
▼
settler ── batch ──▶ Tab contract (Base) ── USDC ──▶ services
▲ rules (budget, allowlist, expiry)15x402 flow
- Agent request → service replies
402withX-Payment-Required(amount, asset, payTo, network, nonce, facilitator). - pahz signer validates rules, signs an authorization for the amount from the tab.
- Retry with
X-Paymentheader. Service or facilitator verifies; serves200. - Authorization is queued for settlement.
pahz implements the client side of the x402 spec unchanged; services need nothing pahz-specific.
16Signer
The signer holds tab signing keys in an HSM-backed service and exposes an API to agents authenticated by per-agent keys. It enforces rate caps and price bands, writes meter lines, and queues authorizations. It cannot move funds outside a settlement, and settlements are bounded by the tab contract. Self-hosting the signer is supported.
17Tab contract on Base
A minimal contract per tab holding USDC. Encodes owner, budget (balance), allowlist root, expiry, and tap state. settle() accepts a batch of authorizations and reverts if any pay-to is outside the allowlist or the total exceeds balance. Owner can topUp, withdraw, setRules, setTap.
18Settlement batching
Batches are per tab. Gas is amortised across all payments in the batch; at 200 payments per settlement the per-payment cost is a small fraction of a cent. Batches are ordered by nonce; a failed batch is retried without the offending authorization and the failure is metered.
19Fees
- Per payment: a small flat fee added to the settlement, covering gas and signer operation.
- Holds: free.
- Tab open / close / top-up: gas only.
Live values: pahz fees.
20Security model
| if this is compromised | attacker can | attacker cannot |
|---|---|---|
| the agent | spend the tab within its rules | exceed budget, rate or allowlist; extract a key |
| the signer | sign authorizations within rate/band | settle outside allowlist or above balance (contract reverts) |
| the owner key | everything | — use a hardware wallet or multisig |
21pahz.tab.open()
pahz.tab.open(o: {
budget: number; // USDC
rate?: { perMin?: number; perHour?: number; perDay?: number };
allow: string[]; // hosts or "tag:…"
band?: Record<string, number>; // max price per call; "*" default
expires?: string; // "7d"
fundFrom?: Address;
}): Promise<Tab> // { id, address, agentKey }22pahz.fetch()
const fetch = pahz.fetch(tab);
const r = await fetch(url, init);
// on hold: r.status === 402, r.headers.get("x-pahz-held") === "allowlist" | "band" | "rate" | "budget" | "tap_closed"
// r.headers.get("retry-after") set for rate holds23pahz.meter()
pahz.meter(tab).last(n)
pahz.meter(tab).range(from, to)
pahz.meter(tab).byService() // totals
pahz.meter(tab).export("csv")24pahz.tab.close() / topUp()
await pahz.tab.close(tab) // owner signature
await pahz.tab.open(tab) // re-open
await pahz.tab.topUp(tab, 10) // USDC
await pahz.tab.withdraw(tab) // remaining balance to owner25Service-side helper
import { paid } from "@pahz/serve";
app.get("/q", paid({ price: 0.002, payTo: "0x…" }), handler);
// adds x402 402 response + verification; nothing pahz-specific26Contract interface
interface IPahzTab {
function owner() external view returns (address);
function rules() external view returns (Rules memory); // allowlistRoot, expiry, tap
function settle(Auth[] calldata auths, bytes32[] calldata proofs) external; // settler only
function setRules(Rules calldata r) external; // owner
function setTap(bool open) external; // owner
function topUp(uint256 amt) external;
function withdraw(uint256 amt) external; // owner
}27Errors
| held reason | meaning | owner action |
|---|---|---|
allowlist | service not allowed | allow once / always |
band | price above max for service | raise band / allow once |
rate | rate cap reached | none; agent retries after |
budget | tab balance insufficient | top up |
tap_closed | owner closed the tap | open |
expired | tab past expiry | open a new tab |
28Guide: give a research agent a tab
- Open a tab with 5 USDC,
rate.perMin: 60, allowlist[search.api, infer.llm, tag:verified]. - Replace the agent's
fetchwithpahz.fetch(tab). - Run a task. Watch
pahz meter --follow. - Review holds in the inbox; allow the ones that make sense.
- Top up when the meter says so, or set expiry and let it end.
29Guide: fleet budgets
Open one tab per agent under a team owner (multisig). Tag tabs with team:research. pahz meter --team research --by service rolls spend up. Set per-tab expiry to the sprint length so budgets do not linger.
30Guide: add x402 to your service
npm i @pahz/serve(or any x402 server library).- Wrap the route with
paid({price, payTo}). - Register in the directory with tags so tabs can allowlist you by tag.
- Receive USDC at settlement on Base. No invoices.
31Safety checklist
- Budget = acceptable loss.
- Rate cap so a bug takes an hour, not a second.
- Explicit allowlist; use tags sparingly.
- Price bands on inference endpoints especially.
- Owner key on hardware or multisig.
- Set expiry on every tab.
32FAQ
Does the agent hold a key?
No. It holds an agent key that lets it ask the signer to pay from its tab, within rules.
What if a service is down mid-payment?
The authorization is not consumed; the call fails normally and nothing settles.
Can I use it without x402 services?
No. pahz pays x402 endpoints only.
Other chains?
Base only today, where x402 settles in USDC.
Can I self-host the signer?
Yes; the tab contract bounds it either way.
33Glossary
allowlist — permitted services. band — max price per call. hold — refused call awaiting owner. meter — per-tab log. receipt — settlement proof. settlement — batched on-chain payment. signer — pahz key service. tab — funded, ruled account. tap — open/closed state. x402 — HTTP payment-required protocol.