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)

  1. Request the masked endpoint without a session header:
    curl -i ${ORIGIN}/api/<apiId>
    Expected response: HTTP 402 with JSON payment instructions.
    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 */ }
        }
      }
    }
  2. 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).
  3. Poll the endpoint with the session header until unlocked:
    curl -s ${ORIGIN}/api/<apiId>   -H "X-402-Session: 0x<session_id>" -i
    When unlocked, the server returns HTTP 200 with upstream content:
    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 }>
        }
      }
    }
    If the session expires, the server responds with HTTP 402 and code: "session_expired" and includes a fresh pending session_id you can repurchase.

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