Workshop

402 Payment Required

Published on: 2026-02-16

By: Ian McCutcheon

402 Payment Required

What if your API was free until it wasn't?

No API keys. No signup. No pricing page. You hit the endpoint, you get a response. You hit it again, you get another one. You keep going until you hit the rate limit — and then, instead of a dead end, the API tells you exactly how to pay for more. Right there in the response. Machine-readable. Settleable in seconds.

This isn't a thought experiment. It's running in production on DNSlurp, a DNS lookup API I built. You can test it right now:

curl "https://dnslurp.esoup.net/api/lookup?domains=esoup.net&type=A"

Thirty of those in a minute and you'll see what happens next. Not a 429. A 402.

Pay-Through: A 429 is a wall. A 402 is a wall with a door.


The Pattern: 429 and 402 Should Be Compatible

Here's the core idea. When an API rate-limits you, it returns 429 Too Many Requests with headers like this:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 45
Retry-After: 45

The message is clear: you've used your allocation, wait 45 seconds, try again. Every API does this. It's standardized. And it's a dead end — the client can wait, or the client can leave.

Now look at what happens when DNSlurp rate-limits you:

HTTP/1.1 402 Payment Required
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 45
Retry-After: 45
Payment-Required: eyJ4NDAyVmVyc2lvbiI6Mn0=

{
  "x402Version": 2,
  "error": "Rate limit exceeded.",
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "amount": "170000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "description": "DNSlurp: 3-day elevated rate limits"
  }],
  "message": "Pay $0.17 USDC to get 3 days of unlimited access.",
  "retryAfter": 45
}

Look at the rate limit headers. They're identical. X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After — all the same headers a 429 would carry. A client that doesn't understand payments can treat this response exactly like a 429: wait 45 seconds, try again, keep using the free tier. Nothing breaks.

But a client that does understand payments has a second option. The response body carries a machine-readable payment offer. Pay $0.17 in USDC on Base, get three days of unlimited access. Right there in the same response.

A 429 is a wall. A 402 is a wall with a door. That's the design. Same rate limit headers for backwards compatibility. Payment offer for forward capability. One response, two paths.

I'm calling this the Pay-Through pattern: a free tier that doesn't require registration, backed by a paywall that activates only when the free tier runs out — and the entire negotiation happens inside HTTP. You don't pay to get in. You pay to get through.


The Forgotten Status Code

HTTP 402. Literally named "Payment Required." They knew exactly what it was for. But the spec marked it "reserved for future use" because the payment mechanism was never defined. They named the door but never built the handle. For almost thirty years the industry has collectively shrugged and kept reserving it. Meanwhile, every API on the internet that wants to charge money does it out-of-band: sign up on a website, enter a credit card, get an API key, paste it into your code.

The protocol that was supposed to handle payments natively never got the chance.


What It Looks Like End to End

Here's the full flow, as it actually works on DNSlurp today.

Step 1: Free tier. You make requests. No key, no signup, no auth header. You get 30 lookups per minute.

curl "https://dnslurp.esoup.net/api/lookup?domains=cloudflare.com&type=MX"

Every response includes rate limit headers so you know where you stand:

X-RateLimit-Limit: 30
X-RateLimit-Remaining: 14
X-RateLimit-Reset: 38

Step 2: Rate limit. On request 31, you get a 402. The response carries the rate limit headers (so your existing retry logic still works) and the payment offer (so you can pay if you want to).

Step 3: Payment. If you choose to pay, you sign an x402 payment — $0.17 USDC on Base — and send the signed payload in a Payment-Signature header on your next request. The server verifies and settles the payment through Coinbase's facilitator. This takes seconds.

Step 4: Access token. The server responds with a bearer token:

{
  "message": "Payment accepted. Use this API key for 3 days of elevated access.",
  "accessToken": "eyJhbGciOiJIUzI1...",
  "expiresAt": "2026-02-19T08:00:00.000Z",
  "usage": "Include as Authorization: Bearer <accessToken> in subsequent requests."
}

Step 5: Unlimited access. Use the token in an Authorization header. Rate limits are bypassed for three days. The server confirms your status on every response:

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1..." \
  "https://dnslurp.esoup.net/api/lookup?domains=esoup.net&type=A"

X-Paid-Access: active
X-Paid-Expires: 2026-02-19T08:00:00.000Z

When the token expires, you drop back to the free tier. No subscription. No recurring charge. Pay again when you need to.


The x402 Protocol

The payment mechanics use x402, an open protocol that puts payment negotiation into HTTP headers. The client hits the rate limit, sees the payment offer, settles it through a crypto wallet, and gets a time-limited API token back — all in the same HTTP conversation.

On the server side, I verify and settle payments through Coinbase's CDP facilitator. The facilitator checks the payment signature, settles the transaction on Base, and returns a confirmation. I issue a stateless, cryptographically signed bearer token — no database, no session store. The token carries its own expiry and permissions. The server validates it on every request without looking anything up.

On the client side, the @x402/fetch library wraps the standard fetch API. It intercepts 402 responses, prompts a wallet signature, and retries with the signed payment — three lines of code:

import { wrapFetchWithPaymentFromConfig } from '@x402/fetch';

const payingFetch = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: 'eip155:8453', client: walletClient }],
});

const response = await payingFetch('https://dnslurp.esoup.net/api/lookup?domains=esoup.net&type=A');

No signup. No dashboard. No credit card form. The payment happened inside the protocol, between the client and the server, without leaving the terminal.


Free First, Paid If You Need It

I have opinions about this.

Most APIs gate everything behind authentication. You can't even see a response without an API key, and you can't get an API key without creating an account. The API's first interaction with a potential user is a registration form.

With a 402-aware rate limit, the API's first interaction is a result. You make a request, you get a response. You make thirty requests, you get thirty responses. On the thirty-first, you get a choice: wait a minute, or pay for more. You've already seen the product. You already know if it's useful. The payment decision happens after value has been demonstrated, not before.

This is how street vendors work. Try the sample. If you like it, buy some. The 402 response is the sample jar.

Every API that hides behind a registration wall is asking users to commit before they've seen value. That's not a business model. That's a friction generator. The APIs that win adoption are the ones you can curl without a key. A 402-aware rate limit gives you that generosity and a revenue path. You don't have to choose.


Why Now

Three things converged to make 402 practical:

Stablecoins on fast chains. USDC on Base settles in seconds for fractions of a cent in gas. Micropayments of $0.17 are economically viable. That wasn't true on Ethereum mainnet at $20 per transaction.

The x402 protocol. An open standard that defines how servers express payment requirements and how clients submit payment proofs, all in HTTP headers. It's the missing handshake that RFC 2616 left undefined in 1997.

Stateless tokens. Once payment settles, the server issues a self-contained, cryptographically signed token. No database. No session store. The token carries its own expiry, its own permissions, its own proof of validity. The server validates it on every request without looking anything up.


Go Try It

DNSlurp is live at dnslurp.esoup.net. The API is documented at dnslurp.esoup.net/api.html. Hit the rate limit, see the 402, pay seventeen cents if you want to. The free tier works fine. The paid tier works fine. Both paths are real.

HTTP gave us a status code for "this resource costs money" almost thirty years ago. The protocol designers knew this interaction would be needed. They couldn't build the payment rail, so they reserved the code and moved on. Twenty-nine years of "reserved for future use."

The payment rail exists now. The proof is in the pudding. If you're building an API with a free tier, consider what happens when your 429 becomes a 402.

The Pay-Through pattern. Free until you need more. Paid when you choose. No signup. No friction. Just HTTP, doing what it was always supposed to do.

Stop reserving 402.


Questions, issues, or ideas? All eSoup projects are discussed through the GitHub link at the bottom of this page.