ESA Pay-per-Crawl
Verification Provider Integration Specification

This specification is for third-party providers who want to offer AI-crawler pay-per-crawl access control to websites accelerated by ESA. Implement one verify API per this spec and submit your integration parameters — ESA users can then select you as a provider in the console, fill in your endpoint and key, and go live with zero custom development on the ESA side.

integration_mode: verification BYOK neutral integration Draft · for review

1Overview

AI crawlers (GPTBot, ClaudeBot, PerplexityBot, …) keep increasing their crawl volume on content sites, while publishers lack fine-grained control such as "charge per request / allow for free / block outright". ESA (Edge Security Acceleration) provides a Pay-per-Crawl capability at its edge nodes: it identifies crawler traffic and, per the publisher's configuration, calls a third-party provider to decide "allow or not, charge or not".

ESA uses a BYOK (Bring Your Own Key) neutral integration: the platform binds to no single provider — it only defines the contract. Two integration standards exist today:

Standardintegration_modeHow ESA worksWhere pricing & accounts live
Standard 1: Verification (this document)verificationBefore origin fetch, ESA calls your verify API out-of-band → reads the verdict → allows or blocksProvider side hosts publisher rules, crawler accounts, billing and balances
Standard 2: x402x402The ESA edge acts as resource server, returns 402 + payment requirements, calls a facilitator's verify/settle for settlementPricing configured on the ESA side per rule; provider acts as settlement facilitator
This document defines Standard 1: Verification only. The x402 standard (on-chain stablecoin / fiat settlement) is defined in a separate specification. To the ESA user both standards feel the same: pick a provider → fill in parameters → switch on.

The three things you need to do

  1. Implement one verify API (§5–§8): accept ESA's out-of-band call and return a JSON verdict containing accessGranted;
  2. Operate your own platform (§10): host publisher rules & pricing, crawler registration / token issuance / balance top-up — all through your own channels, ESA is not involved;
  3. Hand the publisher two parameters (§11): your verify Endpoint plus the Gateway Key you issued — the publisher enters them in the ESA console and integration is complete; optionally submit a Provider Profile (§12) to enter the console's provider dropdown.

2Roles & Architecture

RoleResponsibility
AI Crawler / AgentThe party fetching content. Registers an account and tops up balance on your platform; carries the crawler token you issued on every request.
ESA Edge GatewayThe enforcer of the paywall: intercepts crawler requests at the edge, calls your verify API out-of-band before fetching origin, then either fetches origin (per accessGranted) or relays your 402/403 verdict verbatim to the crawler.
Verification Provider (you)The decision maker: hosts publisher rules, crawler accounts, billing and balances; returns the allow/deny verdict. Never touches the content itself.
PublisherThe content owner — both an ESA user and your customer: configures URL rules and pricing on your platform, and enters your endpoint and key in the ESA console.

ESA Edge Gateway

1 request content

4 allow → fetch origin

4' deny → relay 402 / 403

2 verify · 3 verdict

5 register / top up

Verification Provider (your platform)

verify API
POST verify_endpoint

Publisher rules / pricing

Crawler accounts / balance / top-up

AI Crawler / Agent
holds crawler token

Intercept before origin fetch
call verify out-of-band
allow / block per verdict

Publisher origin
(site accelerated by ESA)

Fig. 1 · Architecture: ESA enforces (intercept / allow / relay), the provider decides (rules / accounts / billing). Crawler registration and top-up go through the provider's own channels (dotted line 5) — ESA is not involved.

Responsibility boundary: verify is a pure decision API — it returns a JSON verdict only, never page content. ESA fetches the content from origin after an allow verdict, so your service naturally never handles the publisher's content data.

3End-to-End Workflows

3.1 Paid access (main flow)

Publisher OriginVerification ProviderESA Edge GatewayAI CrawlerPublisher OriginVerification ProviderESA Edge GatewayAI Crawlergateway auth → identify crawlermatch rules → check balance → atomic chargeGET /premium/article.htmlX-Crawler-Token: crawler_xxx1POST verify_endpointAuthorization: Bearer gateway_keyrelay X-Crawler-Token + JSON body2200 accessGranted:truecharged / chargeAmount3allowed → fetch content4200 content5200 content to crawler6

Fig. 2 · Paid access sequence: ESA calls verify out-of-band before origin fetch; content is fetched only after the verdict (including the charge) passes.

3.2 Four typical outcomes

Scenarioverify verdictESA behaviorCrawler sees
Free content (allow rule / no rule matched → default allow)200 accessGranted:true, no chargeFetch origin200 + content
Paid content, valid token, sufficient balance200 accessGranted:true + chargedFetch origin200 + content
Paid content, no token / low balance / price negotiation failed402 accessGranted:false + guidanceRelay the 402 JSON verbatim402 + register / top-up guidance
Publisher explicitly blocks (block rule)403 accessGranted:falseRelay the 403 JSON verbatim403 (payment cannot unlock)

3.3 Cold start: how an unknown crawler self-onboards

When a crawler that has never heard of your platform first hits the paywall, the only information it has is the 402 JSON you return. As long as the 402 response is self-describing (§9), crawlers — especially LLM-driven agents — can autonomously complete register → top up → retry with zero prior knowledge:

Phase 2 · Top up & fetch content

Top up balance
(or trial credit)

Retry same URL with
X-Crawler-Token

verify charges OK
accessGranted:true

ESA fetches origin
returns content 200

Phase 1 · Hit the wall & register

Crawler without token
requests paid content

ESA calls verify
402 invalid_token

Crawler reads 402 JSON
onboarding + guidance

Calls provider register API
obtains crawler token

Fig. 3 · Cold-start onboarding: self-describing 402 → self-service registration / top-up → retry the same URL with the token → allowed, content delivered.

ESA's role is "relay": for deny verdicts (402 / 403), ESA hands your response JSON to the crawler verbatim. Onboarding links, the register API and step-by-step instructions all come from your response body — the conversion funnel is entirely in your hands.

4Two-Layer Authentication

The verify API involves two fully independent authentication layers. Keep them apart:

LayerCarrierProvesIssued to / held byOn failure
Gateway layer
(server-to-server)
Authorization: Bearer <gateway_key>"This request really comes from the ESA gateway, and belongs to which publisher's configuration"You issue it to onboarded publishers → publisher pastes it into the ESA console → ESA sends it on every call401 gateway_unauthorized
Crawler layer
(end-user identity)
X-Crawler-Token header
or body crawlerToken
"Which crawler is accessing" — the billing subjectYou issue it to registered crawler operators → crawler sends it → ESA relays it402 invalid_token
Never put the crawler token in Authorization. Authorization is the generic HTTP auth header — CDNs / WAFs / logging platforms often record or rewrite it, and this spec reserves it for the gateway layer. The crawler token travels in the custom X-Crawler-Token header (the header name can be re-mapped in your Provider Profile, §12).

Issuing and rotating the gateway_key

  • Issuance: after a publisher signs up with you, you issue a gateway_key (isolate per site / environment); the publisher pastes it into the ESA console. For you it does two jobs at once: confirms the request came via ESA, and locates the publisher it belongs to.
  • Validation: use constant-time comparison against timing attacks; on failure return 401 without crawler-onboarding fields (this error is for gateway operators, not crawlers).
  • Rotation: you MUST accept multiple valid keys concurrently (old + new) → user swaps the key in the console → old key revoked, with zero downtime.

5Request Specification (ESA → you, input)

ESA issues an HTTPS POST to your endpoint with Content-Type: application/json. Full example:

HTTP
POST /v1/verify HTTP/1.1
Host: verify.example-provider.com
Content-Type: application/json
Authorization: Bearer <gateway_key>              ← gateway-layer auth (§4)
X-Crawler-Token: crawler_a1b2c3...               ← crawler token (relayed by ESA)
X-Original-Crawler-Agent: Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)
X-Request-Id: req-8c85ba19abef566b               ← optional: correlation id (§14)

{
  "publisherDomain": "example-news.com",
  "resourceUrl": "/premium/deep-report.html",
  "crawlerToken": "crawler_a1b2c3...",
  "crawlerAgent": "gptbot",
  "crawlerAutoPrice": true
}

5.1 Request headers

HeaderRequirementDescription
Authorization: Bearer <gateway_key>RequiredGateway-layer authentication (§4).
X-Crawler-TokenRecommendedCrawler identity token, relayed from the crawler's original request. Either this header or body crawlerToken; header wins when both present.
X-Original-Crawler-AgentRecommendedThe original crawler UA, always sent redundantly (§5.3 dual channel).
X-Request-IdOptionalCorrelation id. Echo it back in the response header; generate one yourself if absent (§14.3).

5.2 Request body fields

FieldTypeRequirementDescription
publisherDomainstringRequiredDomain of the visited site (the Host ESA received). Locates the publisher and its rule set. Supporting "subdomain falls back to parent domain" matching is recommended.
resourceUrlstringRequiredPath of the visited resource (e.g. /premium/report.html) or a full URL. Used for URL-rule matching.
crawlerTokenstringOptional*Body channel for the crawler token. May be omitted when the X-Crawler-Token header is present. Requests with no token at all must not be rejected outright — run the normal decision flow and return 402 + onboarding for pay scenarios (§9).
crawlerAgentstringRecommendedBody channel for crawler identity: if ESA already identified the crawler → the normalized identifier (e.g. "gptbot"); otherwise → the raw UA string (§5.3).
crawlerAutoPricebooleanPick oneAuto-charge (recommended; covers the vast majority of traffic in phase 1): charge the resource price directly when balance suffices.
crawlerExactPricestringExact price: format "USD 0.10"; charge only when it exactly matches the resource price.
crawlerMaxPricestringBudget cap: format "USD 0.50"; charge only when resource price ≤ cap.
metadataobjectOptionalPass-through metadata (order id, source tag, …); store it in access logs.

The three price fields are mutually exclusive — return 400 if more than one appears. None present = price discovery mode (§7). Suggested price-string format: ^[A-Z]{3}\s+[0-9]+\.?[0-9]*$ (e.g. USD 0.01); return 400 INVALID_PRICE_FORMAT otherwise.

5.3 Dual-channel crawler identity (why not User-Agent)

When ESA forwards the request as a gateway, the standard User-Agent is rewritten to the gateway's own UA — the original crawler UA would be lost (the same reason X-Forwarded-For exists for client IPs). Crawler identity therefore travels over two complementary custom channels:

ChannelFieldContentPurpose
Body fieldcrawlerAgentIdentified → normalized identifier; not identified → raw UAFast path: direct hit on known crawler types
HTTP headerX-Original-Crawler-AgentAlways the raw crawler UA stringAudit trail + provider-side fallback re-identification

Suggested provider-side identification order: ① direct hit on body identifier → ② regex match on body raw UA → ③ regex match on header raw UA → ④ the type registered on the crawler's account → ⑤ generic bot-signature fallback → ⑥ unknown. Return the result in the response's crawlerIdentification field (§6.3) for ESA-side statistics and publisher reports.

6Response Specification (you → ESA, output)

6.1 The core contract: one boolean

ESA's minimal decision reads a single field: accessGranted. true → fetch origin; false → block and relay your response JSON to the crawler with statusCode. Every other field is value-added information for crawlers / publishers / reconciliation. Return it in every response, including 4xx/5xx.

Three minimal valid responses:

JSON
// allow (free, or charge succeeded)
{ "accessGranted": true,  "statusCode": 200 }

// payment required (price discovery)
{ "accessGranted": false, "statusCode": 402, "requiresPayment": true,
  "error": "price_required", "price": 0.10, "currency": "USD",
  "onboarding": "https://portal.example-provider.com/guide.html" }

// publisher block
{ "accessGranted": false, "statusCode": 403, "error": "access_blocked" }

6.2 Field reference

FieldTypeRequirementDescription
accessGrantedbooleanRequiredThe unified allow/deny flag (§6.1).
statusCodenumberRecommendedHTTP status to return to the crawler (200 / 402 / 403 …); also use it as the verify response's own HTTP status.
messagestringRecommendedOne human- (and LLM-) readable sentence describing the outcome.
errorstringRequired if non-200Machine-readable error code (§8).
requiresPaymentboolean402: recommendedMarks "payment unlocks this" (vs. 403's "payment cannot unlock").
price / currencynumber / stringDiscovery: requiredResource price and ISO 4217 currency (e.g. USD). Include in every 402 when possible so crawlers can budget.
charged / chargeAmountboolean / numberOn chargeWhether and how much this request was charged. ESA may map these to response headers (e.g. X-Charged / X-Charge-Amount).
transactionobjectOptionalTransaction receipt {transactionId, timestamp} for audit and disputes.
crawlerobjectOptionalCrawler account snapshot, e.g. {balance, required, shortfall} — telling a broke crawler exactly how much it is short is a conversion-critical detail.
ruleobjectOptionalMatched-rule summary {urlPattern, accessMode, price, billingType} — helps crawlers understand the billing basis.
crawlerIdentificationobjectRecommendedIdentification result (§6.3). Include in every response, including 402/403.
onboardingstring (URL)402: requiredRegistration / top-up guide URL (§9).
guidanceobjectStrongly recommendedMachine-readable guidance block (§9.2) — the deciding factor for cold-start conversion.
timestampstringRecommendedISO 8601 processing time.

6.3 crawlerIdentification

JSON
"crawlerIdentification": {
  "recognized": true,          // known crawler type identified?
  "identifier": "gptbot",      // normalized id; fixed "unknown" when unrecognized (not null)
  "verified": true             // optional extension: authenticity (tri-state true/false/null)
}

verified is an optional advanced field: UA strings can be forged, so providers may verify authenticity against officially published crawler egress IP ranges, or Web Bot Auth (RFC 9421 HTTP message signatures) — true = confirmed genuine, false = likely spoofed, null = cannot judge. Whether to block on it is publisher policy; start with flag-only.

6.4 Full response example (charge succeeded)

JSON · HTTP 200
{
  "accessGranted": true,
  "statusCode": 200,
  "message": "Access granted - payment processed",
  "charged": true,
  "chargeAmount": 0.10,
  "currency": "USD",
  "rule": { "urlPattern": "/premium/*", "accessMode": "pay", "price": 0.10, "billingType": "per-request" },
  "crawler": { "remainingBalance": 49.90 },
  "transaction": { "transactionId": "txn-9f3a-...", "timestamp": "2026-07-23T10:30:00Z" },
  "crawlerIdentification": { "recognized": true, "identifier": "gptbot", "verified": true },
  "timestamp": "2026-07-23T10:30:00Z"
}

7Price Intent

The crawler expresses "how it is willing to pay" in the request body, and you complete negotiation and charging within a single call. Four modes:

ModeRequest parameterBehaviorTypical use
Auto-charge RecommendedcrawlerAutoPrice: trueCharge the resource price directly when balance suffices — payment completes in one requestAutomated AI-agent crawling, bulk access. Supporting only this mode in phase 1 covers the vast majority of traffic
Price discovery(no price parameter)No charge; return 402 + error: "price_required" + price/currencyFirst-time price exploration, budgeting
Exact pricecrawlerExactPrice: "USD 0.10"Charge only on exact match with the resource price, else 402 price_mismatch (response carries the real price for retry)Two-step confirmed payment (discover, then confirm)
Budget capcrawlerMaxPrice: "USD 0.50"Charge the actual price when resource price ≤ cap, else 402 price_too_highCost control, overspend protection
discovery → confirm (two-step example)
// 1) price discovery: no price parameter
POST /v1/verify   { "publisherDomain": "example-news.com", "resourceUrl": "/premium/a.html", ... }
// ← 402 { "error": "price_required", "price": 0.10, "currency": "USD", "requiresPayment": true }

// 2) confirm: send the exact price back
POST /v1/verify   { ..., "crawlerExactPrice": "USD 0.10" }
// ← 200 { "accessGranted": true, "charged": true, "chargeAmount": 0.10 }
Validation rules: the three price parameters are mutually exclusive (return 400 INVALID_PARAMETER when combined); malformed price strings or unsupported currencies return 400 INVALID_PRICE_FORMAT.

8Status & Error Codes

8.1 HTTP status codes

StatusMeaningaccessGrantedESA behavior
200Allow (free / charge succeeded / default allow)trueFetch origin
400Parameter error (missing field / exclusive conflict / bad price format)falseRelay
401Gateway auth failed (gateway_key missing / invalid)falsePrompt the user to check the console key
402Payment-side action required (see error)falseRelay the 402 JSON verbatim to the crawler
403Publisher block — payment cannot unlockfalseRelay the 403 JSON verbatim to the crawler
500Provider internal errorfalsePer timeout / failure policy (§14.2)
Why is there no 401 for "invalid crawler token"? Missing / invalid crawler tokens map to 402 + error: "invalid_token": to the crawler this is not "rejected" but "register / pay and you're in" — keeping 402's semantics single (payment resolves it), 401 exclusive to the gateway layer, and 403 exclusive to "money won't help". Never mix the three.

8.2 Error codes (the error field)

StatuserrorMeaningCorrect crawler reaction
402invalid_tokenToken missing / invalid / account disabledRegister per onboarding / guidance
402insufficient_balanceBalance < price (include crawler.shortfall)Top up, then retry
402price_requiredPaid resource but no price intent (discovery)Read price, retry with a price intent
402price_mismatchExact-mode price differsRetry with the real price from the response
402price_too_highMax-mode budget below priceRaise budget or skip the resource
403access_blockedPublisher block rule matchedSkip this URL; do not register / top up for it
401gateway_unauthorizedgateway_key missing / invalid(Publisher / ops) check the console key
400MISSING_REQUIRED_FIELD / INVALID_PARAMETER / INVALID_PRICE_FORMATParameter problemsFix the request
500INTERNAL_ERRORProvider internal faultRetry later
Naming convention follows the current contract: 402 / 403 / 401 error codes are lower_snake_case (business-semantic errors); 400 / 500 are UPPER_SNAKE_CASE (technical errors). Use generic wording in 5xx messages — never leak internals (connection strings, stack traces).

9Crawler Onboarding (onboarding & guidance)

Pay-per-crawl is a two-sided market: publishers need revenue, which needs crawlers able to pay. The 402 response is your single most important acquisition channel — ESA relays it verbatim to every crawler that hits the wall. This section codifies what makes unknown crawlers convert autonomously.

9.1 The onboarding field (required in 402)

Points to a guide page URL that explains the payment model and the registration / top-up entry to the crawler (and the humans behind it):

JSON
"onboarding": "https://portal.example-provider.com/guide.html"
  • Must be an absolute URL (the JSON leaves your domain context once relayed);
  • Serve both audiences on the guide page: a human-readable explanation plus a machine-readable full curl flow (register → log in → top up → retry with token);
  • 403 responses may carry onboarding too, but with the opposite message — state clearly that payment cannot unlock this resource and the crawler should not register / top up for it.

9.2 The guidance block (strongly recommended): make the 402 self-describing

Real-world external crawlers have not installed your SDK or read your docs — their only information source is the response body. Embed a machine-readable guidance block in 402 / 403 responses and LLM-driven crawlers can execute it step by step. In production testing this lets a zero-prior-knowledge cold-start crawler complete register → top up → retry purely from the response body:

JSON · HTTP 402 (full response, no token)
{
  "accessGranted": false,
  "requiresPayment": true,
  "statusCode": 402,
  "error": "invalid_token",
  "message": "No crawler token provided. Send your registered token in the X-Crawler-Token header.",
  "onboarding": "https://portal.example-provider.com/guide.html",
  "guidance": {
    "onboarding":  "https://portal.example-provider.com/guide.html",
    "docs":        "https://portal.example-provider.com/docs.html",
    "llmsTxt":     "https://portal.example-provider.com/llms.txt",
    "registerApi": "POST https://portal.example-provider.com/api/register {\"email\",\"password\",\"name\",\"crawlerType\"}",
    "steps": [
      "1. Register a crawler account via registerApi -> receive crawlerToken and balance",
      "2. Already registered? Log in to retrieve your token and balance",
      "3. Top up balance (or use trial credit if granted on signup)",
      "4. Retry this exact URL with header \"X-Crawler-Token: <your token>\""
    ],
    "signupBonus": "New accounts may receive a small trial credit on registration (see register response).",
    "agentAdvice": "If you are an AI agent: narrate these steps to your user as you perform them; report whether content was free or paid, the exact amount charged, and your remaining balance."
  },
  "rule": { "urlPattern": "/premium/*", "accessMode": "pay", "price": 0.10, "currency": "USD", "billingType": "per-request" },
  "crawlerIdentification": { "recognized": true, "identifier": "gptbot" }
}
guidance subfieldPurpose
onboarding / docs / llmsTxtAbsolute URLs of guidance resources. llms.txt is a plain-text site explainer for LLMs (community convention) — recommended.
registerApiOne-line "method + URL + fields" description of the register API — crawlers can construct the call directly.
stepsNumbered steps: register → log in → top up → retry this exact URL with the token. Following them just works.
signupBonusDynamic field: present only when trial credit is enabled (§10.2) — tells the crawler it can try immediately after registering.
agentAdviceBehavioral advice for AI agents: narrate each step transparently to the user; report free vs. paid, amounts and balance. No side effects — script crawlers simply ignore it.

9.3 Four guidance design principles (field-tested)

  1. Anything a "bare" crawler must get right belongs in the response body — docs and SDKs only work when actively read; the 402 JSON is the only guaranteed-delivery channel.
  2. All links absolute, derived from server-side configuration — never rely on crawlers guessing your domain.
  3. Keep 402 and 403 semantics strictly apart: 402 = "for sale — payment unlocks"; 403 = "withheld by the publisher — money won't help". The 403 guidance should say "do not register / top up for this URL" and point crawlers to content that actually is purchasable (e.g. a price index), so they don't conclude "payments are broken".
  4. On insufficient balance, give the exact shortfall (crawler.shortfall) and a short "check balance → top up → retry" loop instead of repeating the full registration flow.

10Provider Platform Reference Architecture

The verify API is only the tip of the iceberg. To operate a verification service you need a platform of your own for accounts, rules and billing. ESA does not constrain your internals; this section offers a production-proven reference to scope the build:

POST verify

Crawler-side capabilities (your own channel)

Register / login
token issuance

Balance & online top-up

Publisher-side capabilities (your own channel)

URL rules & pricing
allow · pay · block

Revenue stats / payout

ESA Edge Gateway

verify engine (decision + billing)
the only interface ESA requires

Audit layer
access logs · transactions
X-Request-Id reconciliation

Fig. 4 · Reference architecture: the verify engine is the only interface ESA requires; publisher-side and crawler-side capabilities run on your own channels.

10.1 Publisher side: rules & pricing

Rule fieldDescription
urlPatternURL matching pattern; support exact / prefix / regex types
accessModeallow (free) / pay (charge) / block (deny; payment cannot unlock)
price / currencyPricing for pay mode. Extensible to per-crawler-type differential pricing (e.g. training vs. search crawlers)
billingTypeBilling type: start with per-request (most common); time passes / subscriptions can come later
priorityMatching priority. Recommend first-match-wins: test from highest priority down; the first hit applies
  • Default-allow paths that match no rule — the paywall should cover only what the publisher explicitly fenced, avoiding collateral damage to normal site pages; optionally offer a publisher-level switch for default-block.
  • Offer bulk import / site content discovery (read the site's sitemap / price manifest to generate candidate rules) to cut publisher setup cost.

10.2 Crawler side: accounts, tokens & top-up

CapabilityKey points
Register / login APIOffer pure-API registration (email + password suffices), referenced by the 402 guidance.registerApi — the first link of cold-start conversion. Return the crawler token and initial balance directly in the register response.
Two separate credentialsIssue the crawler token (long-lived, rides on crawl requests, only used for verify billing) separately from the session credential (short-lived, for account APIs: password change, top-up, statements) — a leaked crawl-path token never endangers the account.
Balance queryProvide a read-only balance endpoint callable with the crawler token itself — crawlers check funds mid-crawl without switching credentials.
Online top-upA hosted checkout (Stripe-Checkout-like) is enough: create payment session → return checkout URL → a human completes payment → balance lands. The crawler hands the URL to its user.
Trial credit (optional)Grant a small credit on signup (a few cents) so crawlers complete the full paid loop before ever paying — a major cold-start conversion boost. Must pair with signup rate-limiting / anti-abuse (per IP, email domain), and write it to the transaction ledger for consistent reconciliation.

10.3 Verify engine: decision flow & billing

fail

pass

block

allow / no match

pay

no

yes

pass

fail

Gateway auth
gateway_key

401 gateway_unauthorized

Identify crawler
locate publisher

Match publisher rules
highest priority first

403 access_blocked

200 free pass / default pass

Crawler token
valid?

402 invalid_token
+ onboarding URL

Price intent &
balance check

atomic charge + ledger
200 accessGranted:true
charged / chargeAmount

402 price_required /
price_mismatch / price_too_high /
insufficient_balance

Fig. 5 · Reference decision flow: gateway auth → identify crawler / locate publisher → rule match → branch by mode → the pay branch validates token, price intent and balance, then charges atomically.

Concurrent charging must be atomic (production lesson): with a separate "check balance, then deduct" sequence, concurrent requests all pass the check and drive the account negative. The deduction must complete in a single guarded atomic operation (e.g. UPDATE balance = balance - ? WHERE id = ? AND balance >= ?; zero affected rows → return 402), guaranteeing no overdraft and no lost money under any concurrency.

10.4 Audit & reconciliation

  • Log every verify call (publisher, resource, crawler, verdict, amount); on charge, write one ledger entry each for the crawler and the publisher;
  • Store X-Request-Id (§14.3) on all three records and join them with ESA-side logs by the same id, per transaction;
  • Give publishers revenue reports (by crawler type / time) and crawlers spending statements — the basis of two-sided trust.

11Integration Guide: the Publisher Fills In Just Two Parameters

When a publisher switches on Pay-per-Crawl in the ESA console, they fill in exactly two parameters — your verify endpoint and the gateway key you issued to them. That completes the integration. From then on, every crawler request — interception, the verify call, origin fetch on allow, verbatim 402/403 relay on deny — is executed automatically by ESA. No further configuration for the publisher, and no per-publisher coordination between you and ESA.

11.1 What the publisher sees in the ESA console (mock-up)

Path: ESA console → Site → Security / Bot management → Pay-per-Crawl → Enable → Verification provider. The configuration form is just these two inputs:

Pay-per-Crawl · Verification provider settings mock-up
https://verify.example-provider.com/v1/verify
The full HTTPS address of your verify API, supplied by you (one address shared by all your customers)
esa_gw_9f8a7b6c5d4e3f2a1b0c
The key you issued to this publisher on your platform; the publisher copies it and pastes it here
Save & Enable

Mock-up of the ESA console form (static illustration, not a real form). Once these two fields are saved, the paywall is live.

11.2 What exactly the two parameters are

ParameterWhat it isWho provides it / where to get itExample
Endpoint The full HTTPS address of the verify API you implemented per §5–§8 and deployed publicly. ESA POSTs to this address for a verdict on every intercepted crawler request. Provided by you (the provider): published in your integration docs, or shown in your dashboard after the publisher signs up. One address shared by all publishers; may differ per environment (test / production). https://verify.example-provider.com/v1/verify
Gateway Key The key you issued to this one publisher. ESA sends it in Authorization: Bearer on every verify call; for you it does two jobs at once — proves the request really comes from ESA, and locates which publisher's configuration it belongs to (§4). Issued by you after the publisher signs up on your platform (e.g. an "API keys" page in your dashboard); the publisher copies it into the ESA console. One key per publisher; isolate per site / environment and support rotation (§4). esa_gw_9f8a7b6c5d4e3f2a1b0c (format is yours; a recognizable prefix is recommended)

11.3 Integration in three steps (provider's view)

  1. Implement and deploy the verify API (the §5–§8 contract) → this yields your Endpoint;
  2. The publisher signs up on your platform (registers, configures rules and pricing) → you issue their Gateway Key;
  3. The publisher enters the two parameters in the ESA console → done. Everything else ESA needs at runtime (site Host, resource path, original crawler UA, crawler token) comes from the edge request itself and is passed to you automatically (§5); rule and price changes take effect on your side in real time.
Why are two parameters enough? Because the verify request/response contract (§5–§8) is fully standardized — ESA only needs to know "where to call" (Endpoint) and "as whom" (Gateway Key); every other input comes from the live request. The precondition is that you implement this spec's default field and header names exactly. If your existing API uses different names, declare a mapping via the Provider Profile in §12 — no code changes needed.

12Provider Profile (Optional: Enter the Console Dropdown)

The §11 integration path requires no submission to ESA at all. The Provider Profile is an optional, advanced step that buys you two things:

  1. Your brand in the console's provider dropdown: when users pick you, the Endpoint auto-fills and they only enter one key — smoother UX and brand exposure;
  2. Field mapping: if your existing API's header / field names differ from this spec's defaults, declare the mapping in the Profile and the ESA adaptation layer converts automatically — no changes to your API.
Integration formWhat the publisher fills inPrecondition
Custom (§11)Endpoint + Gateway Key (two fields)Implement this spec's default contract exactly
Built-in dropdown (this section)Gateway Key only (Endpoint auto-filled)Submit a Provider Profile to ESA

The Profile is a JSON template declaring "how to call me":

JSON · Provider Profile (verification)
{
  "provider": "your-brand",
  "integration_mode": "verification",
  "verify_endpoint": "https://verify.example-provider.com/v1/verify",
  "auth": { "scheme": "bearer", "key_field": "gateway_key" },
  "headers": {
    "crawler_token":  "X-Crawler-Token",             // the header names you actually use
    "original_agent": "X-Original-Crawler-Agent"
  },
  "request_body": {
    "publisher_field":  "publisherDomain",
    "resource_field":   "resourceUrl",
    "auto_price_field": "crawlerAutoPrice"
  },
  "response": {
    "granted_field":    "accessGranted",
    "status_field":     "statusCode",
    "onboarding_field": "onboarding"
  }
}
FieldDescription
providerProvider identifier (display name and brand assets submitted separately).
verify_endpointVerify API address, auto-filled in the console (user-editable for test environments).
authGateway-layer auth scheme. Currently bearer; key_field names the key the console asks users for.
headers / request_body / responseHeader and field mappings. Omit to use this spec's defaults.

Submit alongside the Profile: brand name and logo, guide / docs URLs, technical contact, and a test-environment Endpoint with test keys (for ESA joint acceptance, §13).

Either way, pricing and rules live on your side, not in the ESA console (a key difference from x402) — changes take effect in real time with no ESA round-trip. The smoothness of your onboarding and rule UX is your competitive edge.

13Integration Checklist & Acceptance Test Matrix

13.1 Checklist

#ItemLevel
1Provide an HTTPS POST verify API that returns JSON with accessGranted in every caseMUST
2Validate Authorization: Bearer <gateway_key>; fail with 401 gateway_unauthorized; support concurrent multi-key rotationMUST
3Take crawler identity from the X-Crawler-Token header / body and bill it; never hard-reject token-less requests — route them to 402 onboardingMUST
4Support crawlerAutoPrice auto-charging; carry onboarding in 402 responsesMUST
5Return 403 access_blocked for block scenarios, strictly separated from 402 semanticsMUST
6Follow the §8.2 error codes; charge atomically with no overdraft (§10.3)MUST
7Support discovery / exact / max price intents; return crawlerIdentification; echo X-Request-IdSHOULD
8Embed the guidance block in 402/403 (§9.2); offer pure-API registration and balance querySHOULD
9Provide a test Endpoint / keys to ESA for joint acceptance; submit a Provider Profile (§12) only if you want the console dropdownMUST

13.2 Acceptance matrix: the 12 return values

This matrix covers every return-value combination of the verify API. Trigger and assert each one during joint acceptance — one script self-tests your contract completeness:

#StatuserrorTrigger conditionHow to construct
1200—Allow rule matchedVerify a path configured as allow
2200—No rule matched (default allow)Verify a path with no rule
3200—Paid charge succeededPay path + crawlerAutoPrice:true + funded account; assert exact balance decrease
4402invalid_tokenToken missing / invalid / disabledHit a pay path without X-Crawler-Token; assert onboarding present
5402insufficient_balanceBalance < priceZero-balance account on a pay path; assert crawler.shortfall
6402price_requiredPay path without price intentAssert price / currency returned
7402price_mismatchExact price differs from list pricecrawlerExactPrice with a wrong price
8402price_too_highMax budget < list pricecrawlerMaxPrice below the price
9400INVALID_PRICE_FORMATMalformed price stringcrawlerExactPrice: "notaprice"
10403access_blockedBlock rule matchedVerify a blocked path (must 403 even with a valid token)
11401gateway_unauthorizedGateway key missing / wrongWrong Authorization: Bearer value
12500INTERNAL_ERRORInternal faultNot stably triggerable black-box; confirm the fallback path by code review (documented coverage)
Extra assertions worth adding: after #3, verify the exact deduction via the balance endpoint; hammer one account concurrently to prove no overdraft (§10.3); every response echoes X-Request-Id; and an LLM crawler can complete the flow purely from #4's guidance.steps (cold-start field test).

14Operations & Engineering Recommendations

14.1 Latency budget

verify sits on the critical path of the edge request (synchronously awaited before origin fetch) — your latency adds directly to what end users / crawlers experience:

  • Suggested targets: p50 < 100 ms, p99 < 500 ms including network round-trip; deploy endpoints close to ESA regions;
  • Keep the hot path lean: indexed rule matching, cached identification, no synchronous external calls inside verify;
  • Pure-decision verdicts (allow / block / parameter errors) should return in the ~10 ms range.

14.2 Failure & timeout semantics

  • ESA applies its own timeout and failure policy to verify calls (fail-open pass-through vs. fail-close block, per ESA product configuration). You cannot assume what happens when you are down — the only thing you control is returning fast and unambiguously;
  • On internal faults return 500 + INTERNAL_ERROR with accessGranted:false — never hang the connection;
  • Keep 5xx messages generic; internals go to your own logs only.

14.3 X-Request-Id & reconciliation

HTTP
POST /v1/verify
X-Request-Id: req-8c85ba19abef566b     ← optionally sent by ESA
...
HTTP/1.1 200 OK
X-Request-Id: req-8c85ba19abef566b     ← you echo it; generate one if ESA didn't send it
  • The id runs through your access logs and both ledger entries, joining with ESA-side logs per transaction;
  • Suggested convention: retries reuse the same id — a ready-made hook for future idempotent charging (same id never charges twice), so network retries can't double-bill.

14.4 Security checklist

  • gateway_key and crawler tokens travel over HTTPS only; keys support rotation (§4);
  • Crawler token and account session credentials are separate (§10.2);
  • Trial credit requires signup rate-limiting (per IP / email) against farming;
  • Basic rate limiting and anomaly monitoring on the verify endpoint (against forged "ESA" traffic inflating your ledgers);
  • Store and compare money as fixed-point decimals (DECIMAL), never floats.

14.5 Suggested launch path

  1. Self-test all 12 return values of the §13.2 matrix in your test environment;
  2. Provide a test Endpoint / keys and complete joint debugging with ESA (ESA triggers the real edge chain with a test site); submit a Provider Profile if you want the dropdown listing;
  3. Canary: enable on a test site first; watch latency / verdict distribution / reconciliation consistency;
  4. Enter the console provider list for general availability.