Overview
x402 is an HTTP 402-powered paywall for APIs. You create a masked endpoint that responds with HTTP 402 and on-chain payment instructions until the caller pays. Once paid, requests with the session header are proxied to your upstream API and returned to the caller.
Glossary
- apiId: The identifier for your masked API (path: /api/<apiId>).
- X-402-Session: Header the client sends after payment to unlock content.
- Network: Avalanche C-Chain (chain_id from backend; typically 43114).
- Contract: Gateway contract callers pay to (0xDa90Fac43937AD84dC9483ff118C8c2CEc5f1F56).
- Tokens: AVAX (native), ARENA, GLADIUS (ERC-20).
Create a Masked URL
Two options:
- Use the UI at /gate.
- Call the API: POST /api/402/apis with JSON body:
POST /api/402/apis
Content-Type: application/json
{
"p_name": "My API", // optional
"p_api_url": "https://upstream.example.com/data", // required
"p_merchant_wallet": "0x...", // required, EVM
"p_token_address": "0x...", // AVAX=0x000...000, ARENA, GLADIUS
"p_amount_wei": "100000000000000000", // amount in wei
"p_valid_for_sec": 300, // session duration
"p_chain_id": 43114, // Avalanche C-Chain
"p_fee_bps_snapshot": 100 // optional
}
Response 200:
{ "apiId": "<uuid-or-id>" }Calling the Masked API (Agent Flow)
- Request the masked endpoint without a session header:
Expected response: HTTP 402 with JSON payment instructions.curl -i ${ORIGIN}/api/<apiId>Status: 402 { "code": "payment_required", "data": { "session_id": "0x...", "network": { "chain_id": 43114, "name": "Avalanche C-Chain" }, "contract": "0xDa90Fac43937AD84dC9483ff118C8c2CEc5f1F56", "amount_wei": "...", "merchant_wallet": "0x...", "token_address": "0x...", "calls": { "native": { "fn": "payNativeFor(bytes32 sessionId, address merchant)", "value": "<amount_wei>", "args": ["<session_id>", "<merchant_wallet>"] }, "erc20_approve_then_pay": { "approve": { "spender": "0xDa90...f1F56", "amount": "<amount_wei>" }, "payFor": { "fn": "payFor(bytes32 sessionId, address merchant, address token, uint256 amount)", "args": ["<session_id>", "<merchant_wallet>", "<token_address>", "<amount_wei>"] } }, "erc20_permit": { /* optional permit flow descriptor */ } } } } - Perform on-chain payment using one of the provided call patterns:
- Native AVAX: call payNativeFor(sessionId, merchant) with value=amount_wei.
- ERC-20: approve the contract, then call payFor(sessionId, merchant, token, amount).
- Poll the endpoint with the session header until unlocked:
When unlocked, the server returns HTTP 200 with upstream content:curl -s ${ORIGIN}/api/<apiId> -H "X-402-Session: 0x<session_id>" -i
If the session expires, the server responds with HTTP 402 and code: "session_expired" and includes a fresh pending session_id you can repurchase.Status: 200 { "code": "successful", "data": { "api": { /* full apis_402 row incl. api_url, token, amount, etc. */ }, "session": { "id": <number>, "session_id_hex": "0x...", "expires_at": "<ISO>" }, "upstream": { "status": <http_status>, "content_type": "<mime>", "body": <json|string|{ image_url: string }> } } }
Agent Integration Example (Node + viem/ethers pseudo)
import fetch from 'node-fetch';
// import { walletClient } from './wallet'; // your preconfigured signer on Avalanche C-Chain
async function callMasked(apiId) {
const origin = process.env.ORIGIN || 'https://your.app';
// 1) initial 402
const r1 = await fetch(
origin + '/api/' + apiId
);
if (r1.status !== 402) throw new Error('Expected 402');
const j1 = await r1.json();
const info = j1.data; // includes session_id, contract, token_address, amount_wei
// 2) on-chain payment (choose native or erc20 path shown in info.calls)
// await walletClient.writeContract({
// address: info.contract,
// abi: [...],
// functionName: 'payNativeFor',
// args: [info.session_id, info.merchant_wallet],
// value: BigInt(info.amount_wei),
// chain: avalanche,
// });
// 3) poll until 200
for (let i = 0; i < 45; i++) {
const r2 = await fetch(origin + '/api/' + apiId, {
headers: { 'X-402-Session': info.session_id },
});
if (r2.status === 200) {
const ok = await r2.json();
return ok.data.upstream; // { status, content_type, body }
}
await new Promise((res) => setTimeout(res, 2000));
}
throw new Error('Timeout waiting for unlock');
}Human UI
End-users can visit /<apiId> to complete payment with a wallet and see the unlocked response.
Response Shapes
- 402 payment required: { code: "payment_required", data: { session_id, network, contract, amount_wei, merchant_wallet, token_address, agent_docs, calls, next } }
- 402 session expired: { code: "session_expired", data: {...}, meta: { previous_session, expired_at } }
- 200 success: { code: "successful", data: { api, session, upstream } }
- 404 not found: { code: "not_found" }
Notes
- Set and persist X-402-Session client-side while polling.
- Upstream JSON is returned directly; for images, the server returns { image_url: <api_url> }.
- All payments and sessions are validated server-side against Supabase tables apis_402 and sessions_402.
Contract: 0xDa90Fac43937AD84dC9483ff118C8c2CEc5f1F56 · Network: Avalanche C-Chain