x402station.io specification

v1 · stable

Formal specification of the independent risk-signal layer for x402 agentic commerce. Every section is structured for an LLM consumer reading top-to-bottom. Humans can skim by the section headers; agents should ingest the entire document.

Machine-readable companions: /llms.txt · /api/openapi.json · /.well-known/x402 · /.well-known/agent-card.json · /skill.md · /mcp

For comparative context against other x402 trust services, see /vs.

§0 TL;DR for AI agents

Three lines, in order. If your agent only reads §0, it should still be safe.

// before signing PAYMENT-SIGNATURE for any unfamiliar x402 URL:
const verdict = await fetch("https://x402station.io/api/v1/preflight", {
  method: "POST",
  headers: { "content-type": "application/json", /* x402 PAYMENT-SIGNATURE */ },
  body: JSON.stringify({ url: targetUrl }),
}).then(r => r.json()); // { ok, warnings, metadata }

if (!verdict.ok) {
  // do not pay. consult /api/v1/alternatives for a healthy peer.
  return abort();
}

Cost: $0.001 USDC per pre-flight. Settled via x402 itself. Bulk credits available at $0.0005/call (/guard).

First-call self-test: POST /api/v1/preflight-trial accepts the same {url} body without PAYMENT-SIGNATURE. It is rate-limited to 10 calls per IP per UTC day, one URL per call, and cached for 5 minutes. Production agents should use paid Preflight for fresh data, batch support, and SLA.

§1 Overview

Independent risk-signal layer for x402 agentic commerce — endpoint evidence (decoy / zombie / price-trap / never-paid) before an agent authorizes payment. x402station.io continuously probes x402 endpoints and returns evidence so an AI agent, wallet, or policy engine can decide before authorizing USDC payment. We probe every endpoint in the agentic.market catalog every ~10 minutes, classify results against an open signal vocabulary (§3), and expose live paid agent surfaces:

surfacepurposecadenceprice
Preflightsynchronous pre-flight before paymentper call$0.001 USDC
Forensics7-day diagnostic for one URLad-hoc$0.001 USDC
Watchasync webhook on state-flipsubscription$0.01 / 30 days
Verifiedsupply-side: provider mints a cert30 days$1 USDC

All four surfaces share the same signal vocabulary. The provider that pays for Verified cannot pay to remove a Preflight signal — different paths, different gates.

§2 Authentication / payment

Every paid endpoint follows the canonical x402 v2 challenge-response:

  1. First request returns HTTP 402 Payment Required with the x402 v2 challenge in both places: JSON response body for body-first clients, and payment-required header (base64-encoded mirror) for header-first clients. It also includes www-authenticate: x402.
  2. Client signs the challenge per x402 spec, retries with PAYMENT-SIGNATURE header.
  3. Facilitator (x402.org/facilitator) settles the USDC transfer on Base mainnet (or Base Sepolia for testnet). Server returns HTTP 200 with the response body.

No accounts and no API keys. The signed payment header is the authentication. X-Credit-Id (returned from /api/v1/credits) is an alternative for bulk-prepaid calls.

The only unauthenticated evaluation surface is /api/v1/preflight-trial: free trial for agent self-test, rate-limited and 5-minute cached. It returns the same Preflight verdict shape so client code can switch to /api/v1/preflight without changing its parser.

§3 Signal vocabulary (open spec)

Core signal codes plus x402station operational extensions. Protocol-agnostic — x402 is the first applied profile, but the vocabulary can describe any agent-payment endpoint surface. Used across Preflight / Forensics / Watch where the underlying evidence is available.

codemeaningevidence requirementtone
decoy_price_extremeendpoint listed at ≥ $1,000 USDC per callcurrent price_amount in catalogbad
zombieevery endpoint of this service is failing simultaneously≥2 endpoints, all latest probes 5xx or network errorbad
deadthis single endpoint failing on every probe in last hour≥6 probes in window, 0 healthybad
price_driftendpoint price changed by more than 10× since last snapshotdelta vs prior 24h price_amountwarn
high_concentrationendpoint's provider exceeds catalog concentration thresholdprovider share ≥ 5% of effective catalog supplywarn
wildcard_402provider returns x402 challenges for random non-catalog pathsrandom-path probes confirm catch-all 402 behavior; raw paths collapse to effective=1warn
spa_fallbackprovider returns a 200 HTML shell for random non-catalog pathsrandom-path probes confirm SPA/router fallback behavior; effective supply anchors on sitemap/manifest when availablewarn
proxy_markupendpoint appears to be a third-party proxy/reseller rather than the upstream sourcecatalog text + service identity mismatch + price baselinewarn
recoveredendpoint previously firing a bad signal is now healthytransition: bad → healthy on latest probegood

The vocabulary is protocol-agnostic by design: future profiles (Stripe agent payments, AP2, generic HTTP 402) reuse the same codes. Each profile maps codes to its own evidence shape.

Iron rule: a provider can pay for an audit, but cannot pay to remove a risk signal. Signal classification is 100% probe-data-driven.

§4 Endpoint reference

self-testfree

Free anonymous Preflight trial for agent self-test. No PAYMENT-SIGNATURE required. Limits: 10 calls per IP per UTC day, 50 calls per target host per UTC day, one URL per call, and 5-minute cache TTL. Use paid Preflight for fresh production checks and paid Preflight Batch for bulk routing.

POST /api/v1/preflight-trial
{ "url": "https://api.example.com/x402-endpoint" }

→ { ok, warnings, metadata, risk_score?, confidence?,
    recommended_action?, reason_codes?, observed_at?, evidence? }
per call (Preflight)$0.001 USDC

Synchronous pre-flight check before paying any x402 URL. Agent calls this first; on ok: false, agent must not pay the target.

Body:

{ "url": "https://api.example.com/x402-endpoint" }

Response (200, after PAYMENT-SIGNATURE):

{
  "ok": true | false,                 // false if any critical signal fires
  "warnings": [                       // active signal codes (see §3)
    "decoy_price_extreme" | "zombie" | "dead" | "price_drift" |
    "high_concentration" | "wildcard_402" | "spa_fallback" | "proxy_markup" |
    "no_history" | "unknown_endpoint"
  ],
  "metadata": {
    "service_id": "...",
    "service_name": "...",
    "domain": "...",
    "price": { "amount": "0.005", "currency": "USDC", "network": "Base" },
    "uptime_pct_1h": 98.4,
    "p99_latency_ms": 904,
    "first_seen_at": "2026-04-12T08:14:22Z",
    "category": "Inference",
    "verified": true | false,
    "wildcard_402_detected": true | false,
    "spa_fallback_detected": true | false,
    "effective_count_basis": "sitemap_url_count" | "manifest_resource_count" | "canonical_homepage" | null,
    "proxy_markup": {
      "estimated_markup_ratio": 1.0,
      "basis": "service_median",
      "evidence": ["catalog_text_proxy_or_gateway"]
    }
  }
}
ad-hoc$0.001 USDC

7-day diagnostic for a single URL. Use at integration time, not per-call. See /forensics for full response shape.

POST /api/v1/forensics
{ "url": "https://api.example.com/x402-endpoint" }

→ { url, window_days: 7, uptime_pct, latency_ms: {p50, p90, p99},
    status_breakdown, error_share_pct, signals: [...], price,
    first_seen_at, peer_comparison: { category, rank_in_category, ... } }
subscription$0.01 USDC / 30 days

Subscribe to webhook alerts when an endpoint flips state. 100 prepaid alerts. See /watch for full subscription shape.

POST /api/v1/watch
{
  "url":     "https://api.example.com/x402-endpoint",
  "webhook": "https://your-agent.example/x402-alerts",
  "signals": ["dead", "zombie", "decoy_price_extreme", "price_drift",
              "high_concentration", "wildcard_402", "spa_fallback", "proxy_markup", "recovered"]
}
→ { watchId, expiresAt }

// Webhook delivery (when a signal fires):
POST <your webhook>
Idempotency-Key: <unique>
{
  "watchId": "wch_...",
  "url": "...",
  "signal": "zombie",
  "evidence": { ... },
  "firedAt": "2026-05-05T12:34:56Z"
}
ad-hoc$0.005 USDC

Routing fallback. When Preflight returns ok: false, call this with the flagged URL or a task class hint to get a healthy peer.

POST /api/v1/alternatives
{ "flaggedUrl": "https://api.bad.example/x402", "taskClass": "search" }
→ { alternatives: [ { url, score, reason, ... }, ... ] }
ad-hoc$0.005 USDC

Full blacklist export of currently-flagged endpoints. Use to seed an agent's local denylist; refresh hourly.

GET /api/v1/catalog/decoys
→ { generated_at, count, endpoints: [ { url, signals, evidence }, ... ] }
poll hourly$0.001 USDC

Catalog diff polling. Returns endpoints added or removed since since.

POST /api/v1/whats-new
{ "since": "2026-05-04T00:00:00Z", "limit": 200 }
→ { added_endpoints: [...], removed_endpoints: [...], summary: {...} }
bulk$0.50 USDC = 1000 prepaid

Buy bulk pre-flight credits. Effective rate $0.0005/call (50% off the per-call price). Pass returned creditId via X-Credit-Id on each call; on exhaustion, fall through to per-call x402 automatically.

POST /api/v1/credits
{}  // payment via x402

→ { creditId, balance: 1000, expiresAt, ... }

// later, on every preflight:
POST /api/v1/preflight
X-Credit-Id: <creditId>
{ "url": "..." }
→ HTTP 200 (no x402 settlement; balance auto-decremented)
30 days$1 USDC

Provider-side. Mint a 30-day signed certificate proving an endpoint passes the verified-tier criteria (uptime ≥95% over 7d, p99 < 5,000 ms, prices in $0.0001–$5 USDC band, ≥20 probes per endpoint, no critical signals).

POST /api/v1/verified
{ "url": "https://api.your-service.example/x402-endpoint" }
→ { certificateId, url, tier, issuedAt, expiresAt,
    publicAuditUrl: "https://x402station.io/verified/<id>" }

Pattern certificates use named path placeholders: {id} for one segment, {path+} for one-or-more segments, and {tail*} for zero-or-more segments. Hostnames must stay literal. Public cert JSON returns pattern and covers_child_urls so agents can tell whether a badge covers a concrete URL through a pattern.

§5 Error codes for agents

statusmeaningagent action
200Payment processed, response body returnedUse the response. Continue.
402Payment required (x402 challenge)Sign the challenge, retry with PAYMENT-SIGNATURE header.
400Malformed body (e.g. missing url)Fix the request. Do not retry without changes.
422URL is not a valid x402 endpoint or otherwise unprocessableTreat as ok=false. Surface in agent decision log.
429Rate-limited (very rare; mostly soft limits)Back off ~10s, then retry. Use bulk credits to avoid.
503Statement timeout / probe pipeline degradedTreat as failed pre-flight. Do not pay the target.

§6 Integration patterns

6.1 npm middleware (recommended)

npm install x402station-middleware @x402/fetch @x402/evm viem
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { wrapWithPreflight } from "x402station-middleware";

const x402Fetch = wrapFetchWithPaymentFromConfig(fetch, { schemes: [...] });
const safeFetch = wrapWithPreflight(x402Fetch);

// fail-closed: throws PreflightBlockedError on any critical signal
await safeFetch("https://api.example.com/x402-endpoint", {
  method: "POST",
  body: JSON.stringify({ /* ... */ }),
});

6.2 MCP server (Claude Code / Cursor / Windsurf / Continue)

npx -y x402station-mcp

Exposes ten tools to the host: preflight, forensics, alternatives, catalog_decoys, whats_new, buy_credits, watch_subscribe, plus free credits_status / watch_status / watch_unsubscribe. Auto-signs PAYMENT-SIGNATURE via AGENT_PRIVATE_KEY (Base mainnet wallet).

MCP Registry: io.github.sF1nX/x402station

6.3 Raw API (any HTTP client)

# 1. cold call returns 402 with payment requirements
curl -i -X POST https://x402station.io/api/v1/preflight \
  -H "content-type: application/json" \
  -d '{"url":"https://api.example.com/x402-endpoint"}'
→ HTTP/1.1 402 Payment Required
   payment-required: <base64 challenge>
   www-authenticate: x402
   content-type: application/json; charset=utf-8

{
  "x402Version": 2,
  "resource": { "url": "https://x402station.io/api/v1/preflight" },
  "accepts": [{ "scheme": "exact", "network": "eip155:8453", "...": "..." }]
}

# 2. retry with signed PAYMENT-SIGNATURE header
curl -i -X POST https://x402station.io/api/v1/preflight \
  -H "content-type: application/json" \
  -H "PAYMENT-SIGNATURE: <signed challenge>" \
  -d '{"url":"https://api.example.com/x402-endpoint"}'
→ HTTP/1.1 200 OK
   { "ok": true, "warnings": [], "metadata": {...} }

6.4 Framework templates

Explicitly supported agent surfaces: LangChain, Mastra, Vercel AI SDK, Coinbase AgentKit, Lucid, LangChain JS, and MCP-compatible clients. Use /mcp for MCP host configuration and /api/openapi.json for generated HTTP clients.

  • AgentKit · EVMWalletProvider binding · 4 min setup
  • Cloudflare Agents · Workers runtime + Durable Objects · 5 min setup
  • LangChain / LangGraph · DynamicStructuredTool + MCP adapter · 5 min setup
  • Mastra · wrap the action execution boundary with x402station-middleware or spawn the MCP adapter from /mcp
  • LangChain JS · use the same DynamicStructuredTool boundary as LangChain, or run through any MCP-compatible host.
  • MCP-compatible clients · install x402station-mcp and expose preflight / forensics / alternatives as tools.
  • Lucid / Daydreams · guarded action boundary · 5 min setup
  • Vercel AI SDK · tool execute boundary · 5 min setup

§7 Agent integration patterns

7.1 The fail-closed pre-flight gate

The canonical pattern. Wrap every paid x402 fetch with a Preflight call. On any critical warning, abort.

async function paidFetch(url, init) {
  const verdict = await stationFetch.preflight(url);
  if (!verdict.ok) {
    throw new PreflightBlockedError(verdict.warnings, verdict.metadata);
  }
  return await x402Fetch(url, init);
}

7.2 Routing fallback

When pre-flight blocks a payment, ask /alternatives for a healthy peer in the same task class. Only one extra paid call.

async function paidFetchWithFallback(url, taskClass) {
  const verdict = await stationFetch.preflight(url);
  if (!verdict.ok) {
    const { alternatives } = await stationFetch.alternatives({
      flaggedUrl: url,
      taskClass,
    });
    if (alternatives.length === 0) throw new NoAlternativesError(verdict);
    return paidFetchWithFallback(alternatives[0].url, taskClass); // recurse
  }
  return await x402Fetch(url, /* ... */);
}

7.3 Long-running policy mode

For agents running >1 hour, subscribe a Watch on each integrated URL. The agent doesn't poll pre-flight; it only consults Preflight if the local cache says the URL is dirty (received Watch alert).

// at integration time:
await stationFetch.watchSubscribe({
  url: "https://api.example.com/x402-endpoint",
  webhook: "https://your-agent.example/x402-alerts",
  signals: ["dead", "zombie", "decoy_price_extreme", "high_concentration", "wildcard_402", "spa_fallback"],
});

// in your webhook handler:
app.post("/x402-alerts", (req) => {
  const { url, signal } = req.body;
  agentDirtyCache.add(url, signal);
});

// in the paid path:
async function paidFetch(url, init) {
  if (agentDirtyCache.isDirty(url)) {
    const verdict = await stationFetch.preflight(url);
    if (!verdict.ok) throw new PreflightBlockedError(verdict.warnings);
  }
  return await x402Fetch(url, init);
}

7.4 Bulk pre-flight via credits

For agents making many paid calls per minute, buy 1,000 prepaid credits at $0.50 (50% off per-call rate).

const { creditId } = await stationFetch.buyCredits();
// pass credit id on every call; auto-fallthrough to per-call when balance hits 0
const verdict = await stationFetch.preflight(url, { creditId });

§8 Pricing

surfaceunitprice (USDC)notes
pre-flightper call0.001Preflight sync gate
pre-flight (bulk)per call0.0005via $0.50 → 1000 credits
forensicsper report0.0017-day diagnostic
alternativesper call0.005routing fallback
catalog/decoysper call0.005full blacklist export
whats-newper call0.001catalog diff polling
watchper 30 days0.01100 prepaid alerts
verifiedper 30 days1.00supply-side mint

All prices in USDC on Base mainnet (eip155:8453). Settled via x402.org/facilitator.

§9 Rate limits & quotas

  • No per-IP / per-account rate limit for paid endpoints — payment is the throttle.
  • Pre-flight statement timeout: 5 s. On timeout, server returns 503; agent should treat as ok: false.
  • Free endpoints (credits/status, watch/status) are softly rate-limited at 60 rpm per IP.
  • Webhook delivery for Watch retries 5× with exponential backoff (1s / 2s / 4s / 8s / 16s) before burning a quota credit and giving up.

§10 Invariants

Properties guaranteed by the implementation. Agents may rely on these.

  1. Signal independence. A provider that pays for Verified status cannot pay to remove a Preflight warning. The two code paths are orthogonal.
  2. Probe transparency. Every signal fires from raw probe data; the evidence shape is public in §3 and the source SQL is open in the public mirror.
  3. Settlement idempotency. Each endpoint accepts the same payment_tx at most once. Retries with the same signed payment return the prior response, never double-charge.
  4. Catalog identity. An endpoint is identified by canonical URL (lowercased host, no trailing slash, query params normalised). The same URL always maps to the same endpoint_id.
  5. Time monotonicity. All timestamps are UTC ISO 8601 from a single Postgres clock. No client timestamps cross the API boundary.

§11 Machine-readable surfaces

For AI scrapers / agent runtimes that prefer structured ingestion over this HTML page:

§12 End-to-end examples

12.1 Minimal Bun agent

import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { wrapWithPreflight } from "x402station-middleware";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY!);
const x402Fetch = wrapFetchWithPaymentFromConfig(fetch, {
  account,
  facilitator: "https://x402.org/facilitator",
});
const safeFetch = wrapWithPreflight(x402Fetch);

const res = await safeFetch("https://api.exa.ai/search", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ query: "x402 agent payments" }),
});
console.log(await res.json());

12.2 Cloudflare Workers

export default {
  async fetch(request, env) {
    const account = privateKeyToAccount(env.AGENT_PRIVATE_KEY);
    const x402Fetch = wrapFetchWithPaymentFromConfig(fetch, { account });
    const safeFetch = wrapWithPreflight(x402Fetch);
    try {
      const r = await safeFetch(targetUrl, { method: "POST", body });
      return r;
    } catch (e) {
      if (e instanceof PreflightBlockedError) {
        return new Response(JSON.stringify({ blocked: e.warnings }), { status: 422 });
      }
      throw e;
    }
  }
}

12.3 LangChain DynamicStructuredTool

const safePayTool = new DynamicStructuredTool({
  name: "safe_x402_pay",
  description: "Pay an x402 endpoint after pre-flight check. Use for any paid HTTP call.",
  schema: z.object({ url: z.string().url(), body: z.unknown() }),
  func: async ({ url, body }) => {
    const r = await safeFetch(url, { method: "POST", body: JSON.stringify(body) });
    return await r.text();
  },
});

§13 Contact / changelog

Bugs, feature requests, edge cases, ideas: hello@x402station.io. Operator input shapes the roadmap directly.

Changelog and 60-day data: /blog. Source mirrors: x402station-mcp · x402station-middleware.

This specification covers v1 (stable). v2 will add a protocol-agnosticx402-signals open-spec repository; the signal vocabulary in §3 is the v2 candidate.