Payments & x402
Keyless, per-request agent payments in USDC on Base using the x402 HTTP transport
What is x402?
x402 turns HTTP 402 Payment Required into a working payment rail. When an
autonomous agent calls a paid Krawl endpoint without paying, the server answers
with a machine-readable challenge describing the exact price, asset, and
receiving wallet. The agent signs a USDC payment authorization, retries with an
X-PAYMENT header, and gets the result.
This matters for agents because it removes every human step:
- No account, no signup. There is nothing to register.
- No API key. The payment itself is the authorization.
- No human in the loop. An agent discovers the price, pays, and gets work done in a single retry, all over plain HTTP.
Payments settle in USDC on Base. Krawl never custodies funds or touches a private key. A facilitator service verifies the signature and broadcasts the on-chain settlement.
Krawl implements x402 specification v1 (HTTP transport). The version constant surfaced in every response is
x402Version: 1. Spec: github.com/coinbase/x402.
Pricing
Krawl charges per research request, by tier:
| Tier | Price | Atomic USDC units | Env var |
|---|---|---|---|
Quick krawl (mode=quick) | $1.00 | 1000000 | X402_PRICE_SHALLOW |
Deep krawl (mode=deep) | $5.00 | 5000000 | X402_PRICE_DEEP |
USDC has 6 decimals, so prices are expressed in atomic units: 1000000 =
$1.00. Both prices are configurable via the Krawl server's environment
variables (see Server x402 configuration). The
amount advertised in the challenge comes through the maxAmountRequired field
as a string.
Which endpoints are gated
x402 gates only the research endpoints. Everything else stays on the normal API-key auth path.
| Endpoint | x402 gated? | Notes |
|---|---|---|
POST /research | Yes | Deep or quick depending on mode |
POST /research/upload | Yes | Deep research with uploaded documents |
POST /chat/completions and POST /v1/chat/completions | Yes | OpenAI-compatible surface |
POST /templates/{template_id}/research | Yes | Template execution |
POST /research/async | No | Requires an API key (no keyless x402) |
POST /search | No | Requires an API key |
GET /results, /templates, /sessions, etc. | No | Standard API-key auth only |
Self-hosting warning: x402 protects the research endpoints, but the other endpoints (
/results,/templates,/sessions, and friends) use regular API-key auth. If you enable x402 without settingKRAWL_DEV_KEY, those other endpoints stay publicly accessible and the server logs a warning on startup. SetKRAWL_DEV_KEYto protect them.
The payment flow
Here is the exact sequence Krawl runs, with the real field names from the implementation.
1. Agent calls a gated endpoint with no payment
The agent POSTs to a research endpoint without an X-PAYMENT header (and
without an API key).
curl -i -N -X POST https://api.krawl.sh/research \
-H "Content-Type: application/json" \
-d '{"query": "What is EIP-4844?", "mode": "quick"}'2. Server returns HTTP 402 with a payment-requirements challenge
The server responds 402 Payment Required. The body is rendered at the top
level (not nested under a detail key) so standard x402 clients can read
accepts and x402Version directly. A WWW-Authenticate: Payment header is
also emitted for agents that speak the Merchant Payment Protocol.
HTTP/1.1 402 Payment Required
Content-Type: application/json
WWW-Authenticate: Payment realm="krawl", methods="x402", discovery="https://api.krawl.sh/discovery/resources"{
"x402Version": 1,
"error": "X-PAYMENT header is required",
"accepts": [
{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "1000000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xYourReceivingWalletAddress",
"resource": "https://api.krawl.sh/research",
"description": "Krawl quick research (pay-per-use, non-refundable)",
"mimeType": "text/event-stream",
"outputSchema": null,
"maxTimeoutSeconds": 120,
"extra": {
"name": "USDC",
"version": "2"
}
}
]
}The accepts array holds one PaymentRequirements object per acceptable
payment. maxAmountRequired is the price for the requested tier (here, 1000000
for a quick krawl). extra.name and extra.version carry the USDC EIP-712
domain name and version used to construct the signature.
3. Agent signs a USDC authorization and retries with X-PAYMENT
The agent builds an EIP-3009 transfer authorization that pays at least
maxAmountRequired USDC to payTo, signs it, and wraps it in a
PaymentPayload. That payload is JSON, base64-encoded, and sent in the
X-PAYMENT header. The shape Krawl decodes and validates is:
{
"scheme": "exact",
"network": "base",
"payload": {
"authorization": {
"to": "0xYourReceivingWalletAddress",
"value": "1000000"
}
}
}curl -i -N -X POST https://api.krawl.sh/research \
-H "Content-Type: application/json" \
-H "X-PAYMENT: eyJzY2hlbWUiOiJleGFjdCIsIm5ldHdvcmsiOiJiYXNlIiwicGF5bG9hZCI6ey4uLn19" \
-d '{"query": "What is EIP-4844?", "mode": "quick"}'Most agents will use an x402 client library (for example, the Coinbase x402 SDK) to construct and sign this header automatically.
4. Server verifies, settles, and streams the cited report
Before trusting the facilitator, Krawl does a defense-in-depth check that the
signed authorization matches the challenge: same scheme, same network, the
payload.authorization.to address equals payTo, and the authorized value is
greater than or equal to maxAmountRequired (overpaying is allowed). A
mismatch returns 402; a malformed/undecodable X-PAYMENT header returns
400.
If that passes, Krawl calls the facilitator's /verify endpoint, then (after
reserving a concurrency slot) its /settle endpoint to broadcast the transfer.
Both calls send { "x402Version": 1, "paymentPayload": ..., "paymentRequirements": ... }.
On success the server streams the cited research report as Server-Sent Events
(mimeType: text/event-stream) and attaches an X-PAYMENT-RESPONSE header.
That header is a base64-encoded JSON settlement receipt:
{
"success": true,
"transaction": "0x…",
"network": "base",
"payer": "0x…"
}If settlement fails, the response carries an additional errorReason field and
the request is rejected with 402. If the server is at capacity it returns
503 with an empty accepts array without charging, since the slot is
reserved before payment is settled.
Discovering prices without triggering a 402
Agents can fetch the full payable-resource catalog up front:
curl https://api.krawl.sh/discovery/resources{
"x402Version": 1,
"enabled": true,
"facilitator": "https://x402.org/facilitator",
"resources": [
{
"resource": "https://api.krawl.sh/research",
"method": "POST",
"description": "Deep multi-source research report (cited markdown).",
"x402": {
"scheme": "exact",
"network": "base",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"maxAmountRequired": "5000000",
"priceUsd": 5.0,
"payTo": "0xYourReceivingWalletAddress"
}
}
],
"payment_methods": ["x402", "api_key"]
}Network and asset details
| Setting | Value | Env var |
|---|---|---|
| Network | base (mainnet); base-sepolia for testnet | X402_NETWORK |
| Asset | USDC | X402_ASSET_NAME |
| Asset contract (Base mainnet) | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | X402_ASSET |
| Asset (EIP-712 domain) version | 2 | X402_ASSET_VERSION |
| Facilitator | https://x402.org/facilitator | X402_FACILITATOR_URL |
| Max payment timeout | 120 seconds | X402_MAX_TIMEOUT_SECONDS |
The facilitator handles both /verify and /settle. The verify/settle timeout
is aligned with maxTimeoutSeconds so a Base settlement broadcast has room to
confirm under congestion without a premature client timeout.
Server x402 configuration
x402 turns on automatically once a receiving wallet address is set on the
Krawl server. This section is operator reference — it documents how the
Krawl x402 surface is configured. Hosted Krawl
(api.krawl.sh) ships with these defaults; the env vars are listed here so
the contract between client and server is explicit.
# Enable x402 by setting your receiving wallet (0x… on Base)
X402_PAY_TO=0xYourReceivingWalletAddress
# Choose the network: "base" (mainnet) or "base-sepolia" (testnet)
X402_NETWORK=base
# Prices in atomic USDC units (6 decimals). Defaults shown.
X402_PRICE_SHALLOW=1000000 # $1.00 per quick krawl
X402_PRICE_DEEP=5000000 # $5.00 per deep krawl
# Optional: protect non-research endpoints when x402 is on
KRAWL_DEV_KEY=your-shared-keyFor the full list of environment variables, see Getting Started.
x402 and API keys are alternatives
A request authenticated with a valid API key is not charged via x402. The research endpoints accept either form of access:
- API key present (
X-API-Keyheader): the request is authorized by the key and billed against the key's plan/quota. x402 does not run. - No API key, x402 enabled: the request must carry a valid
X-PAYMENTheader and pays per request in USDC.
You never pay twice. If you hold a key, just send it. See Authentication for keys, plans, and quotas, and Agents & MCP for connecting autonomous agents to Krawl.
Quickstart — runnable code
Trigger a live 402 challenge (curl)
The fastest way to confirm x402 is alive: call a gated endpoint with no key
and no X-PAYMENT. The response body is the challenge.
curl -i -X POST https://api.krawl.sh/research \
-H "Content-Type: application/json" \
-d '{"query": "What is EIP-4844?", "mode": "quick"}'You will see HTTP/1.1 402 Payment Required and a JSON body with accepts[0].maxAmountRequired = "1000000" (one quick krawl, $1.00).
Python — sign + pay with the x402 SDK
# pip install x402 web3 httpx
import os, httpx
from eth_account import Account
from x402.client import X402Client
pk = os.environ["AGENT_PRIVATE_KEY"] # 0x… Base wallet
account = Account.from_key(pk)
client = X402Client(account=account, http=httpx.Client(timeout=600))
resp = client.post(
"https://api.krawl.sh/research",
json={"query": "What is EIP-4844?", "mode": "quick"},
headers={"Accept": "text/event-stream"},
)
# x402 SDK auto-handled the 402 challenge, signed an EIP-3009 transfer,
# replayed with X-PAYMENT, and the server is now streaming SSE.
for line in resp.iter_lines():
if line.startswith("data: "):
print(line)
print("settled:", resp.headers.get("X-PAYMENT-RESPONSE"))Node — sign + pay with the x402 SDK
// npm i x402 viem
import { createX402Client } from "x402";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);
const client = createX402Client({ account });
const resp = await client.fetch("https://api.krawl.sh/research", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "text/event-stream" },
body: JSON.stringify({ query: "What is EIP-4844?", mode: "quick" }),
});
// X-PAYMENT was attached, server settled, body is the SSE stream
const reader = resp.body!.getReader();
const dec = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
process.stdout.write(dec.decode(value, { stream: true }));
}
console.error("settled:", resp.headers.get("X-PAYMENT-RESPONSE"));Testnet — Base Sepolia
Point the same code at Base Sepolia by setting the network on your x402
client (or, in a local dev environment, on the server). The challenge will
advertise network: "base-sepolia" and Sepolia USDC, and the facilitator
will verify against the testnet:
# Local dev: enable testnet on the server.
X402_NETWORK=base-sepolia
X402_ASSET=0x036CbD53842c5426634e7929541eC2318f3dCF7e # USDC on Base Sepolia# Python x402 client — explicit network selection
X402Client(account=account, network="base-sepolia")// Node x402 client — explicit network selection
createX402Client({ account, network: "base-sepolia" })Funded Sepolia USDC is available from the Circle USDC faucet.
Verify settlement after the call
Every successful x402 response carries X-PAYMENT-RESPONSE (base64 JSON):
echo "<X-PAYMENT-RESPONSE value>" | base64 -d | jq{
"success": true,
"transaction": "0xabc...",
"network": "base",
"payer": "0x..."
}Use transaction to look the transfer up on
BaseScan (https://sepolia.basescan.org for
testnet). If the facilitator's success flag is false, an errorReason
field explains why and the request was rejected with 402.