Guide

End-to-End: Paying an x402 API

A research agent needs data from an API that charges $0.50 per call using the x402 “402 Payment Required” standard. The agent must never pay unless your AgentOps policy allows it. Here is the whole flow.

Step 0 — Platform setup (once)

  • Deploy the AgentOps server → it lives at your base URL.
  • Install @agops-labs/sdk in the agent project — it is published on npm.

Step 1 — Provision the agent (sk_)

ts
const operator = createAgentOpsClient({
  baseUrl: 'https://<your-agentops-host>',
  apiKey: process.env.AGENTOPS_OPERATOR_KEY!,
});

const { agent, api_key } = await operator.createAgent({ name: 'Research Agent' });
// then set its policy in the console: spend cap, per-tx max $1, allowlist the data API

Step 2 — The agent hits the paywall

The agent calls the data API. It replies 402 Payment Required with an x402 body describing the amount and payee. The agent does not pay directly.

ts
const res = await fetch('https://data.example/v1/report');
if (res.status === 402) {
  const paymentRequired = await res.json(); // the x402 body — pass it verbatim
}

Step 3 — Ask AgentOps to authorize (ag_)

ts
const asAgent = createAgentOpsClient({
  baseUrl: 'https://<your-agentops-host>',
  apiKey: api_key, // ag_live_...
});

const decision = await asAgent.authorize({
  kind: 'x402-payment',
  agentId: agent.id,
  idempotencyKey: 'report-2026-07-24',
  paymentRequired,
});

The server runs the full firewall. Two possible outcomes:

  • DENY — e.g. over the spend cap or the API isn’t allowlisted. The agent stops. Nothing was paid.
  • ALLOW — the response carries a signed payment_header the agent can attach.

Step 4 — Complete the purchase

ts
if (decision.outcome === 'DENY') {
  throw new Error('blocked by policy: ' + decision.reason);
}

const paid = await fetch('https://data.example/v1/report', {
  headers: { [decision.payment_header.name]: decision.payment_header.value },
});
const report = await paid.json(); // the agent got its data — because policy allowed it

Steps 2–4, in one call

Those three steps are the same every time, so the SDK ships them as createGuardedFetch. It does exactly what you just read — 402, authorize, attach header, retry — behind a normal fetch signature:

ts
import { createGuardedFetch, PaymentDeniedError } from '@agops-labs/sdk';

const guardedFetch = createGuardedFetch({
  baseUrl: 'https://<your-agentops-host>',
  apiKey: api_key,      // ag_live_...
  agentId: agent.id,
});

try {
  const paid = await guardedFetch('https://data.example/v1/report');
  const report = await paid.json(); // the agent got its data — because policy allowed it
} catch (err) {
  if (err instanceof PaymentDeniedError) {
    console.log('blocked by policy:', err.reason, '| decision:', err.decisionId);
  }
}

NOTE

Same firewall, same server-side signing, same audit trail — only the boilerplate moves into the SDK. Write the steps out by hand when you need to inspect or branch on the decision before paying.

Step 5 — Proof is anchored (automatic)

Your reconcile worker reads settlement from Casper and writes SHA-256(decision) into the GuardRegistry. There is now a tamper-proof, publicly verifiable record of exactly what this agent was authorized to pay — with no trust in you required.

NOTE

Prefer non-blocking? Call authorize(input, { async: true }) to get a decisionId, then poll operator.getDecision(decisionId). Same pipeline, same anchor.