Overview
PAY//AGENT is a proposed library that sits between an AI agent's HTTP client and its payment signer. When a server answers 402 Payment Required, the library checks the request against a policy file, then either authorizes the payment, denies it with a reason code, or holds it for a person.
Status: proposed. The package name @payagent/client below is a placeholder and is not published. Do not install anything under that name.
Quickstart
The intended setup has three steps: write a policy, wrap the agent's fetch, and pass a signer that never leaves its own process.
import { loadPolicy, withPolicy } from "@payagent/client"; // proposed, not published
const policy = await loadPolicy("./policy.json");
// signer: your isolated x402-capable signer, e.g. built on the official x402 client libraries
const fetchWithPolicy = withPolicy(fetch, { policy, signer, ledger: "./ledger.db" });
const res = await fetchWithPolicy("https://api.example/v1/search?q=x402");
if (res.ok) console.log(await res.json());
Until a release exists, the playground runs the same rule order in your browser with example data.
Proposed client and server flow
Client side (PAY//AGENT)
- Send the request unchanged.
- On a 402, parse the payment requirements from the response body.
- Drop requirements outside the network and asset allowlists. Pick the cheapest remaining one.
- Evaluate the policy and write the verdict to the ledger.
- On allow, ask the signer for a payment payload and retry with the x402 payment header.
- Record the response and any settlement header as a receipt.
Server side (unchanged x402)
PAY//AGENT does not require server changes. A server that follows the x402 specification returns a 402 challenge, verifies the retried payment, settles it through its facilitator, and returns the resource. Use the official x402 server middleware for this side.
Example HTTP 402 challenge
This follows the shape of the public x402 version 1 payment requirements. Field names can change between specification versions; check the official repository before relying on them.
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402Version": 1,
"error": "X-PAYMENT header is required",
"accepts": [
{
"scheme": "exact",
"network": "base-sepolia",
"maxAmountRequired": "2000",
"resource": "https://api.example/v1/search",
"description": "One search query (EXAMPLE)",
"mimeType": "application/json",
"payTo": "<payee address from the service>",
"maxTimeoutSeconds": 60,
"asset": "<token contract address>"
}
]
}
Amounts are strings in the asset's base units. For a six-decimal stablecoin, "2000" means 0.002 units. Addresses are left as placeholders on purpose.
Policy configuration
A policy is one JSON file per agent. All amounts are decimal strings in whole units of the asset, converted to base units at load time.
{
"version": "0.1",
"agent": "research-bot",
"active": true,
"networks": ["base-sepolia"],
"assets": [{ "network": "base-sepolia", "address": "<token contract you choose>", "decimals": 6 }],
"payees": {
"mode": "allowlist",
"entries": [{ "origin": "https://api.example", "payTo": "<payee address>" }]
},
"limits": {
"perRequest": "0.10",
"rolling": { "window": "24h", "max": "5.00" },
"escalateAtOrAbove": "1.00"
},
"quotes": { "maxAgeSeconds": 60 },
"replay": { "trackNonces": true, "retainFor": "7d" }
}
| Key | Meaning |
|---|---|
active | Set to false to revoke. Every request is denied with PA_POLICY_INACTIVE. |
networks | Allowed network identifiers, as they appear in challenges. |
assets | Allowed token contracts per network. |
payees | allowlist pins payees per origin; any disables the check. |
limits | Per-request cap, rolling budget and escalation threshold. |
quotes | Maximum age of a challenge before it must be refetched. |
replay | Whether and how long to remember used nonces. |
Receipt format
One receipt per payment decision, including denials. Receipts are local records; they are not proof of settlement unless they include the server's settlement response.
{
"id": "rcpt_example_0001",
"status": "SIMULATED",
"agent": "research-bot",
"policyHash": "sha256:<hash of policy.json>",
"request": { "method": "GET", "url": "https://api.example/v1/search" },
"challenge": { "scheme": "exact", "network": "base-sepolia", "amount": "2000", "payTo": "<payee>" },
"verdict": { "decision": "allow", "codes": [], "evaluatedAt": "2026-11-04T12:00:00Z" },
"result": { "httpStatus": 200, "settlement": null }
}
Errors
Denials return a structured error to the agent, so it can explain the block instead of retrying. Codes are proposed.
| Code | Cause | What to do |
|---|---|---|
PA_POLICY_INACTIVE | Policy or agent revoked | Re-enable the policy if the revocation was a mistake |
PA_NETWORK_NOT_ALLOWED | Challenge uses an unlisted network | Add the network only if you monitor it |
PA_ASSET_NOT_ALLOWED | Challenge uses an unlisted asset | Check the token contract with the service |
PA_PAYEE_NOT_ALLOWED | Payee differs from the pinned entry | Treat as suspicious; confirm out of band |
PA_OVER_REQUEST_CAP | Amount above the per-request cap | Raise the cap or route to escalation |
PA_OVER_BUDGET | Rolling budget would be exceeded | Wait for the window or raise the budget |
PA_QUOTE_EXPIRED | Challenge older than the allowed age | Request again to get a fresh challenge |
PA_REPLAY_DETECTED | Nonce already used | Do not resend; investigate the source |
PA_ESCALATION_REQUIRED | Amount at or above the approval threshold | Approve or reject in your approval channel |
Security guidance
- Keep the signing key out of the agent's process, prompt and tools.
- Fund the spending wallet with only what you can lose, and top it up deliberately.
- Start on a testnet. Move to mainnet only after reviewing receipts for a while.
- Pin payees for every service you pay. Treat a changed payee as an incident.
- Store the policy where the agent cannot write to it.
- Read the full security model before connecting real funds to anything.