x402station.io specification
v1 · stableFormal 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:
| surface | purpose | cadence | price |
|---|---|---|---|
| Preflight | synchronous pre-flight before payment | per call | $0.001 USDC |
| Forensics | 7-day diagnostic for one URL | ad-hoc | $0.001 USDC |
| Watch | async webhook on state-flip | subscription | $0.01 / 30 days |
| Verified | supply-side: provider mints a cert | 30 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:
- First request returns
HTTP 402 Payment Requiredwith the x402 v2 challenge in both places: JSON response body for body-first clients, andpayment-requiredheader (base64-encoded mirror) for header-first clients. It also includeswww-authenticate: x402. - Client signs the challenge per x402 spec, retries with
PAYMENT-SIGNATUREheader. - 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.
| code | meaning | evidence requirement | tone |
|---|---|---|---|
decoy_price_extreme | endpoint listed at ≥ $1,000 USDC per call | current price_amount in catalog | bad |
zombie | every endpoint of this service is failing simultaneously | ≥2 endpoints, all latest probes 5xx or network error | bad |
dead | this single endpoint failing on every probe in last hour | ≥6 probes in window, 0 healthy | bad |
price_drift | endpoint price changed by more than 10× since last snapshot | delta vs prior 24h price_amount | warn |
high_concentration | endpoint's provider exceeds catalog concentration threshold | provider share ≥ 5% of effective catalog supply | warn |
wildcard_402 | provider returns x402 challenges for random non-catalog paths | random-path probes confirm catch-all 402 behavior; raw paths collapse to effective=1 | warn |
spa_fallback | provider returns a 200 HTML shell for random non-catalog paths | random-path probes confirm SPA/router fallback behavior; effective supply anchors on sitemap/manifest when available | warn |
proxy_markup | endpoint appears to be a third-party proxy/reseller rather than the upstream source | catalog text + service identity mismatch + price baseline | warn |
recovered | endpoint previously firing a bad signal is now healthy | transition: bad → healthy on latest probe | good |
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
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? }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"]
}
}
}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, ... } }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"
}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, ... }, ... ] }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 }, ... ] }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: {...} }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)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
| status | meaning | agent action |
|---|---|---|
200 | Payment processed, response body returned | Use the response. Continue. |
402 | Payment required (x402 challenge) | Sign the challenge, retry with PAYMENT-SIGNATURE header. |
400 | Malformed body (e.g. missing url) | Fix the request. Do not retry without changes. |
422 | URL is not a valid x402 endpoint or otherwise unprocessable | Treat as ok=false. Surface in agent decision log. |
429 | Rate-limited (very rare; mostly soft limits) | Back off ~10s, then retry. Use bulk credits to avoid. |
503 | Statement timeout / probe pipeline degraded | Treat 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-middlewareor 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-mcpand 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
| surface | unit | price (USDC) | notes |
|---|---|---|---|
| pre-flight | per call | 0.001 | Preflight sync gate |
| pre-flight (bulk) | per call | 0.0005 | via $0.50 → 1000 credits |
| forensics | per report | 0.001 | 7-day diagnostic |
| alternatives | per call | 0.005 | routing fallback |
| catalog/decoys | per call | 0.005 | full blacklist export |
| whats-new | per call | 0.001 | catalog diff polling |
| watch | per 30 days | 0.01 | 100 prepaid alerts |
| verified | per 30 days | 1.00 | supply-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.
- Signal independence. A provider that pays for Verified status cannot pay to remove a Preflight warning. The two code paths are orthogonal.
- 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.
- Settlement idempotency. Each endpoint accepts the same
payment_txat most once. Retries with the same signed payment return the prior response, never double-charge. - 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. - 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:
- /llms.txt — short AI-scraper summary (RFC for llms.txt standard)
- /api/openapi.json — OpenAPI 3.1 spec covering every paid + free endpoint
- x402station.io Preflight Dataset v0.1 — CC-BY-4.0 weekly aggregate probe snapshot; raw probe time-series stays gated
- /.well-known/x402 — x402 protocol manifest (resource list, payment contracts, networks)
- /.well-known/agent-card.json — Agent Card spec
- /.well-known/mcp/server-card.json — MCP server card
- /skill.md — agent skill definition (single-file ingestible)
- /mcp — HTML install guide for the x402station MCP adapter
§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.