Pay Per Crawl

Charge AI crawlers per request with HTTP 402 challenges and a single-use token ledger.

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

FieldTypeDefaultDescription
pricefloatunsetPrice emitted in the challenge body and the price= parameter of the challenge header.
currencystringUSDISO-4217 code surfaced in the challenge.
headerstringcrawler-paymentHeader the crawler reads from the 402 response and writes to its retry.
crawler_user_agentslistGPTBot, ChatGPT-User, ClaudeBot, anthropic-ai, Google-Extended, PerplexityBot, CCBot, FacebookBotCase-insensitive substring matches against the request User-Agent. An empty list treats every GET / HEAD as a crawler.
valid_tokenslist[]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-After header on 402; crawlers retry as soon as they have a token.

See also