What it does
The ai_crawl_control policy implements the Pay Per Crawl pattern. AI crawlers that arrive without a valid Crawler-Payment token receive a 402 Payment Required response with a JSON challenge. A crawler that wants the content posts a payment to your billing system out of band, retries with the issued token in the Crawler-Payment header, and the proxy validates the token through a pluggable ledger. Each token redeems exactly once.
An in-memory ledger seeded from config covers a single node; the HTTP ledger adapter (HMAC-SHA256 signed JSON over HTTPS, with idempotency keys and a circuit breaker) lets the proxy ask an external ledger service to redeem tokens, so a fleet of replicas shares one spend record.
Wire shape
crawler GET /article
User-Agent: GPTBot/1.0
proxy <- 402 Payment Required
Crawler-Payment: realm="ai-crawl" currency="USD" price="0.001"
Content-Type: application/json
body: {"error":"payment_required","price":"0.001","currency":"USD",
"target":"blog.example.com/article","header":"crawler-payment"}
crawler GET /article (after paying out of band)
User-Agent: GPTBot/1.0
crawler-payment: tok_a89be2...
proxy <- 200 OK
body: <article>
crawler GET /article (replay attempt)
crawler-payment: tok_a89be2...
proxy <- 402 (single-use ledger; token already spent)
Configuration
policies:
- type: ai_crawl_control
price: 0.001
currency: USD
header: crawler-payment # default
crawler_user_agents: # case-insensitive substring match
- GPTBot
- ChatGPT-User
- ClaudeBot
- anthropic-ai
- Google-Extended
- PerplexityBot
- CCBot
valid_tokens: # in-memory ledger
- tok_a89be2f1
- tok_b7cf012e
- tok_c34f9a82
Field reference
| Field | Type | Default | Description |
|---|---|---|---|
price | float | unset | Price emitted in the challenge body and the price= parameter of the challenge header. |
currency | string | USD | ISO-4217 code surfaced in the challenge. |
header | string | crawler-payment | Header the crawler reads from the 402 response and writes to its retry. |
crawler_user_agents | list | GPTBot, ChatGPT-User, ClaudeBot, anthropic-ai, Google-Extended, PerplexityBot, CCBot, FacebookBot | Case-insensitive substring matches against the request User-Agent. An empty list treats every GET / HEAD as a crawler. |
valid_tokens | list | [] | Seeds the in-memory ledger. Each token redeems once, then leaves the set. |
Only GET and HEAD requests are charged today. POST, PUT, PATCH, and DELETE pass through.
Settlement
Tokens are opaque to the gateway. How they are minted and paid for is up to the ledger. Production setups commonly issue tokens through one of three rails:
- x402 stablecoin. Crawlers settle on Base, Solana, or Polygon and receive a token redeemable at the proxy.
- Stripe MPP. Multi-rail (cards, wallets, stablecoin, Bitcoin Lightning) for human-buyer flows.
- Customer billing systems. Issue tokens out of an existing partner contract or invoicing platform.
Replace the in-memory ledger with the HTTP-callable adapter for multi-replica deployments.
Pairing with Web Bot Auth
Combine identity (bot_auth) with metering (ai_crawl_control) to give yourself two independent gates:
origins:
"blog.example.com":
action: { type: proxy, url: https://upstream.example }
authentication:
type: bot_auth
agents: [...]
policies:
- type: ai_crawl_control
price: 0.001
valid_tokens: [...]
A signed crawler still pays per request unless its Crawler-Payment token redeems. An unsigned client never reaches the policy.
Limitations
- The in-memory ledger is single-process. For multi-replica deployments where one token must spend across nodes, deploy with sticky session affinity to one replica or use the HTTP ledger.
- Detection is User-Agent based. Crawlers that lie about their UA bypass the check; layer this with WAF and bot detection for defence in depth.
- The policy does not currently emit a
Retry-Afterheader on 402; crawlers retry as soon as they have a token.
See also
- Web Bot Auth for cryptographic identity verification on the same request.
- Content for agents to negotiate Markdown for token-priced AI crawlers.