Pay per Request with x402
Call supported read endpoints without an API key by settling USDC on Base
x402 lets an agent or application pay for a supported Y2 read request without creating an account or API key. Y2 advertises an x402 v2 payment requirement, verifies and settles the signed payment, then serves the normal endpoint response.
Choose bearer auth or x402
| Mode | Use it for |
|---|---|
| Bearer API key | Recurring integrations, workspace-scoped data, writes, and plan rate limits |
| x402 | Anonymous, one-off, agent-driven access to supported public read operations |
A request with Authorization: Bearer ... always takes the API-key path. An invalid bearer token
returns its normal 401 or 403; Y2 does not silently fall back to x402. Omit Authorization
entirely when you intend to pay per request.
Supported API surfaces
x402 is enabled on registered read operations in these families:
- Reports, including Markdown, text, signals, graph, audio, and TTS text;
- News items, recaps, and feed discovery;
- OSINT feeds and country intelligence;
- Intel v2 incidents, entities, markets, financial intelligence, signals, cyber, and changes.
It is not an alternative for API-key-only surfaces such as profile management, webhook
configuration, or Agent Y2. Check an operation's security and x-x402 fields in the
generated endpoint reference before building a payment flow.
Some list routes need explicit public context
Anonymous callers do not have a user or workspace context. For example, GET /reports requires
a canonical profileId query parameter on the x402 path.
Read the payment challenge
Send the exact resource request without a bearer key:
curl --include \
"https://api.y2.dev/api/v1/news?topics=markets&limit=5"The 402 response includes a base64-encoded PAYMENT-REQUIRED header and the decoded requirement
as JSON. Its shape is similar to:
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "https://api.y2.dev/api/v1/news?topics=markets&limit=5",
"description": "Real-time direct-source news terminal items",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "2000",
"payTo": "0x...",
"maxTimeoutSeconds": 60,
"extra": { "name": "USD Coin", "version": "2" }
}
],
"extensions": {
"bazaar": { "discoverable": true }
}
}Do not hard-code asset, payTo, EIP-712 domain fields, or the amount from this example. Read and
validate the returned requirement every time. amount is expressed in atomic token units; USDC
uses six decimals, so "2000" represents $0.002.
Production requirements use Base mainnet (eip155:8453). Development deployments can use Base
Sepolia (eip155:84532). The network is a CAIP-2 identifier, not the legacy base slug.
Make a paid request in TypeScript
The following flow matches the x402 packages used by the current Y2 implementation:
npm install @x402/core@^2.18.0 @x402/evm@^2.18.0 viem@^2.55.2Create the signer in a server-side runtime or secure wallet environment. Never expose a raw private key in browser code.
import { x402Client } from "@x402/core/client";
import { x402HTTPClient } from "@x402/core/http";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const privateKey = process.env.X402_PRIVATE_KEY as `0x${string}` | undefined;
if (!privateKey) throw new Error("X402_PRIVATE_KEY is required");
const signer = privateKeyToAccount(privateKey);
const core = new x402Client().register("eip155:*", new ExactEvmScheme(signer));
const x402 = new x402HTTPClient(core);
const url = "https://api.y2.dev/api/v1/news?topics=markets&limit=5";
const challengeResponse = await fetch(url, { headers: { Accept: "application/json" } });
if (challengeResponse.status !== 402) {
throw new Error(`Expected an x402 challenge, received ${challengeResponse.status}`);
}
const challengeBody = await challengeResponse.json();
const requirement = x402.getPaymentRequiredResponse(
(name) => challengeResponse.headers.get(name),
challengeBody,
);
const option = requirement.accepts[0];
if (!option || option.scheme !== "exact" || option.network !== "eip155:8453") {
throw new Error("Y2 returned an unsupported payment option");
}
const maxAtomicUsdc = 2_000n;
if (BigInt(option.amount) > maxAtomicUsdc) {
throw new Error("Payment exceeds the client price ceiling");
}
const payment = await x402.createPaymentPayload(requirement);
const paidResponse = await fetch(url, {
headers: {
Accept: "application/json",
...x402.encodePaymentSignatureHeader(payment),
},
});
const settlement = paidResponse.headers.has("PAYMENT-RESPONSE")
? x402.getPaymentSettleResponse((name) => paidResponse.headers.get(name))
: null;
const result = await paidResponse.json();
console.log({ status: paidResponse.status, settlement, result });The price ceiling above is specific to the $0.002 News example. Set a ceiling appropriate to the
operation you selected. Keep the first and second request URL, method, query, and body identical.
Understand the headers
| Header | Direction | Meaning |
|---|---|---|
PAYMENT-REQUIRED | Response | Base64-encoded x402 v2 challenge |
PAYMENT-SIGNATURE | Request | Base64-encoded signed payment payload |
PAYMENT-RESPONSE | Response | Base64-encoded settlement result |
Authorization | Request | Selects bearer authentication when present |
X-PAYMENT and X-PAYMENT-RESPONSE are legacy x402 v1 names. New Y2 clients should use the v2
headers above.
Prices and wallet limits
Each operation maps to one current tier. The generated endpoint page is the source of truth for the operation's exact price.
| Tier | Price | Per wallet/minute | Per wallet/day |
|---|---|---|---|
| Health/meta | $0.001 | 60 | 10,000 |
| Standard read | $0.002 | 60 | 5,000 |
| Enriched data | $0.01 | 30 | 2,000 |
| Premium report list | $0.05 | 10 | 500 |
| Premium single report | $0.25 | 10 | 200 |
| Premium report audio | $0.50 | 5 | 100 |
Y2 also applies a global per-endpoint circuit breaker. A wallet limit returns 429; an endpoint
capacity limit returns 503. Both are checked before settlement, so the rejected request is not
charged.
Store and inspect a receipt
The EIP-3009 authorization inside the signed payment contains a nonce. Treat that nonce as a receipt lookup capability and store it with the request result:
curl "https://api.y2.dev/api/v1/x402/receipts/$PAYMENT_NONCE"The lookup is public to anyone who knows the nonce. It returns the payer address, endpoint, USD amount, network, status, and—when available—the transaction hash and settlement time. It does not return the signature, token authorization, pay-to address, or full payment payload.
| Receipt status | Meaning |
|---|---|
verify_failed | The facilitator rejected the payment |
rate_limited | Wallet or endpoint limits blocked the request before settlement |
replay_blocked | The payer and nonce combination was already recorded |
settle_failed | Verification passed but settlement did not complete |
settled | Payment settled and Y2 invoked the resource handler |
handler_failed | Payment settled, then the resource handler threw |
Settlement happens before resource execution
A settled payment can still be followed by an application response such as 400, 404, or a
temporary data-readiness error. Validate resource IDs and parameters before signing, inspect the
paid response status and body, and retain the nonce and PAYMENT-RESPONSE metadata for support.
Handle failures
| Status | Typical cause |
|---|---|
401 or 403 | Invalid bearer key or missing bearer scope; no x402 fallback |
402 | Missing or invalid payment, unsupported payment payload, or nonce replay |
429 | Per-wallet minute or daily limit exceeded |
503 | x402 unavailable, settlement infrastructure unavailable, or endpoint circuit breaker open |
| Other endpoint status | Payment settled, then the normal resource handler returned its own result |
Never reuse an authorization nonce. When a result is ambiguous, check the receipt before creating a new payment so you do not confuse a settled request with a transport failure.