x402: Pay-Per-Request API Access
HoudiniSwap Partner API v2 supports the x402 payment protocol as an alternative to traditional API key authentication. With x402, you pay for each API request using USDC on supported EVM chains — no account registration or API key required.
How It Works
The x402 protocol uses the HTTP 402 Payment Required status code to enable pay-per-request access:
Client HoudiniSwap API Facilitator
| | |
| 1. GET /v2/tokens | |
|--------------------------->| |
| 2. 402 Payment Required | |
| (PAYMENT-REQUIRED header)| |
|<---------------------------| |
| | |
| 3. Sign USDC authorization| |
| (EIP-3009, gasless) | |
| | |
| 4. GET /v2/tokens | |
| (PAYMENT-SIGNATURE hdr) | |
|--------------------------->| |
| | 5. Verify & settle |
| |-------------------------->|
| | 6. Settlement confirmed |
| |<--------------------------|
| 7. 200 OK (data) | |
|<---------------------------| |
- Client makes a request without authentication
- Server responds with
402 and a PAYMENT-REQUIRED header containing payment instructions
- Client signs a USDC
transferWithAuthorization (EIP-3009) — no gas required
- Client retries the request with the signed payment in the
PAYMENT-SIGNATURE header
- Server forwards the payment to the facilitator for on-chain verification and settlement
- Facilitator confirms the USDC transfer
- Server returns the requested data
Supported Networks
| Network | CAIP-2 ID | USDC Contract |
|---|
| Base (default) | eip155:8453 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
Additional EVM networks (Ethereum, Polygon, Arbitrum) can be enabled — contact us for availability.
Pricing
Requests are priced by operation type in USDC:
| Operation | Price (USDC) | Endpoints |
|---|
| Read | $0.0001 | GET /tokens, GET /chains, GET /swaps, GET /minMax, GET /rateLimits, POST /dex/approve, POST /dex/allowance, POST /dex/chainSignatures |
| Quote | $0.001 | GET /quotes |
| Exchange | $0.01 | POST /exchanges, POST /dex/confirmTx |
| Status | $0.0001 | GET /orders, GET /orders/{id} |
GET /v2/health and GET /v2/openapi.json are always free and do not require payment or authentication.
Rate Limits
x402 payers are rate-limited to 60 requests per minute per payer address. This is separate from API key rate limits.
x402 vs API Key Authentication
| Feature | x402 | API Key |
|---|
| Registration required | No | Yes |
| Pay model | Per-request | Subscription/tier |
| Authentication | USDC payment | Authorization header |
| Rate limits | 60 req/min per address | Tier-based |
| Best for | Agents, bots, ad-hoc usage | High-volume integrations |
When both are available, API key authentication takes priority. If a request includes an Authorization header (full API key access) or a partner-id header (public read-only partner access), the x402 payment flow is bypassed entirely.
Quick Start
Prerequisites
- An EVM wallet with USDC on a supported network
- Node.js 18+ (for the JavaScript client)
- USDC on Base (amounts are tiny — a full exchange flow costs ~$0.012 total)
1. Install Dependencies
npm install @x402/fetch @x402/evm viem
2. Create a Payment Client
import { x402Client, x402HTTPClient } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
// Create signer from your wallet's private key
const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
// Create x402 client and register EVM payment scheme
const client = new x402Client();
registerExactEvmScheme(client, { signer });
const httpClient = new x402HTTPClient(client);
3. Make Paid API Requests
const BASE_URL = "https://api-partner.houdiniswap.com";
const x402Fetch = async (url: string, options: RequestInit = {}) => {
// Initial request
const res = await fetch(url, options);
if (res.status !== 402) return res;
// Parse payment requirements from 402 response
const paymentRequired = httpClient.getPaymentRequiredResponse(
(name) => res.headers.get(name),
undefined,
);
// Sign the USDC payment (gasless EIP-3009)
const payload = await client.createPaymentPayload(paymentRequired);
const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
// Retry with payment proof
return fetch(url, {
...options,
headers: { ...options.headers, ...paymentHeaders },
});
};
// Get tokens — automatically handles 402 → pay → retry
const response = await x402Fetch(`${BASE_URL}/v2/tokens?term=BTC`);
const data = await response.json();
console.log(data.tokens);
4. Check Payment Receipt
Successful paid responses include a payment-response header:
const paymentResponse = response.headers.get("payment-response");
if (paymentResponse) {
const receipt = JSON.parse(Buffer.from(paymentResponse, "base64").toString());
console.log(receipt);
// {
// "success": true,
// "transaction": "0xabc...def",
// "network": "eip155:8453",
// "payer": "0x..."
// }
}
Full Exchange Flow Example
This example demonstrates a complete swap flow using x402 payments:
import { x402Client, x402HTTPClient } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const client = new x402Client();
registerExactEvmScheme(client, { signer });
const httpClient = new x402HTTPClient(client);
const BASE = "https://api-partner.houdiniswap.com/v2";
const x402Fetch = async (url: string, options: RequestInit = {}) => {
const res = await fetch(url, options);
if (res.status !== 402) return res;
const pr = httpClient.getPaymentRequiredResponse((n) => res.headers.get(n), undefined);
const payload = await client.createPaymentPayload(pr);
const headers = httpClient.encodePaymentSignatureHeader(payload);
return fetch(url, { ...options, headers: { ...options.headers, ...headers } });
};
// Step 1: Look up tokens ($0.0001 each)
const btcRes = await x402Fetch(`${BASE}/tokens?term=BTC&hasCex=true`);
const btcData = await btcRes.json();
const btcToken = btcData.tokens.find((t: any) => t.chain === "bitcoin");
const ethRes = await x402Fetch(`${BASE}/tokens?term=ETH&hasCex=true`);
const ethData = await ethRes.json();
const ethToken = ethData.tokens.find((t: any) => t.chain === "ethereum");
// Step 2: Get a quote ($0.001)
const quoteRes = await x402Fetch(
`${BASE}/quotes?from=${btcToken.id}&to=${ethToken.id}&amount=0.01`
);
const quoteData = await quoteRes.json();
const quote = quoteData.quotes[0];
console.log(`Quote: ${quote.amountOut} ETH`);
// Step 3: Create exchange ($0.01)
const exchangeRes = await x402Fetch(`${BASE}/exchanges`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
quoteId: quote.quoteId,
addressTo: "0xYourEthAddress...",
}),
});
const exchange = await exchangeRes.json();
console.log(`Order: ${exchange.houdiniId}, deposit to: ${exchange.depositAddress}`);
// Step 4: Poll status ($0.0001 each)
const checkStatus = async () => {
const res = await x402Fetch(`${BASE}/orders/${exchange.houdiniId}`);
return res.json();
};
let status = await checkStatus();
const DONE = [4, 5, 6, 7, 8]; // completed, expired, failed, refunded, deleted
while (!DONE.includes(status.status)) {
await new Promise((r) => setTimeout(r, 30000)); // Wait 30s
status = await checkStatus();
console.log(`Status: ${status.status}`);
}
Each x402 payment is an on-chain USDC transfer. Allow sufficient time between rapid sequential requests to avoid transaction failures from nonce collisions. For high-frequency access, consider using API key authentication instead.
These headers are set by the x402 SDK and carry the payment data:
| Header | Direction | Description |
|---|
PAYMENT-REQUIRED | Response (402) | Base64-encoded JSON with payment instructions (price, network, payTo address) |
PAYMENT-SIGNATURE | Request | Base64-encoded signed payment payload (client sends this on the retry request) |
payment-response | Response (200) | Base64-encoded settlement receipt (transaction hash, payer, network) |
HoudiniSwap adds the x402 headers to CORS allow/expose lists so browser-based clients can access them:
| Header | Value added |
|---|
Access-Control-Allow-Headers | X-PAYMENT (allows clients to send payment headers) |
Access-Control-Expose-Headers | X-PAYMENT-RESPONSE (allows clients to read settlement receipts) |
The CORS header names (X-PAYMENT, X-PAYMENT-RESPONSE) are the aliases used by the x402 SDK internally. The actual protocol headers your code reads/writes are PAYMENT-REQUIRED, PAYMENT-SIGNATURE, and payment-response as listed above.
The PAYMENT-REQUIRED header decodes to:
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "https://api-partner.houdiniswap.com/v2/tokens",
"description": "HoudiniSwap API - GET /tokens",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "100",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x...",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USDC",
"version": "2"
}
}
]
}
The amount field is in USDC atomic units (6 decimals). 100 = $0.0001 USDC.
Other Languages
The x402 protocol has official client libraries for multiple languages:
See the x402 Quickstart for Buyers for setup instructions in each language.
Error Handling
| Scenario | Response | Action |
|---|
| No auth header, x402 enabled | 402 with PAYMENT-REQUIRED | Sign payment and retry |
| Payment signature invalid | 402 with payment-response error | Check signing key and payload |
| Facilitator unavailable | 503 Service Unavailable | Retry later or use API key auth |
| Rate limit exceeded (x402) | 429 Too Many Requests | Wait and retry (60 req/min limit) |
| API key present | Normal auth flow | x402 is bypassed entirely |
Further Reading