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.
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:
| Standard | integration_mode | How ESA works | Where pricing & accounts live |
|---|---|---|---|
| Standard 1: Verification (this document) | verification | Before origin fetch, ESA calls your verify API out-of-band → reads the verdict → allows or blocks | Provider side hosts publisher rules, crawler accounts, billing and balances |
| Standard 2: x402 | x402 | The ESA edge acts as resource server, returns 402 + payment requirements, calls a facilitator's verify/settle for settlement | Pricing configured on the ESA side per rule; provider acts as settlement facilitator |
The three things you need to do
- Implement one verify API (§5–§8): accept ESA's out-of-band call and return a JSON verdict containing
accessGranted; - 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;
- 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
| Role | Responsibility |
|---|---|
| AI Crawler / Agent | The party fetching content. Registers an account and tops up balance on your platform; carries the crawler token you issued on every request. |
| ESA Edge Gateway | The 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. |
| Publisher | The 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. |
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.
3End-to-End Workflows
3.1 Paid access (main flow)
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
| Scenario | verify verdict | ESA behavior | Crawler sees |
|---|---|---|---|
| Free content (allow rule / no rule matched → default allow) | 200 accessGranted:true, no charge | Fetch origin | 200 + content |
| Paid content, valid token, sufficient balance | 200 accessGranted:true + charged | Fetch origin | 200 + content |
| Paid content, no token / low balance / price negotiation failed | 402 accessGranted:false + guidance | Relay the 402 JSON verbatim | 402 + register / top-up guidance |
| Publisher explicitly blocks (block rule) | 403 accessGranted:false | Relay the 403 JSON verbatim | 403 (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:
Fig. 3 · Cold-start onboarding: self-describing 402 → self-service registration / top-up → retry the same URL with the token → allowed, content delivered.
4Two-Layer Authentication
The verify API involves two fully independent authentication layers. Keep them apart:
| Layer | Carrier | Proves | Issued to / held by | On 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 call | 401 gateway_unauthorized |
| Crawler layer (end-user identity) | X-Crawler-Token headeror body crawlerToken | "Which crawler is accessing" — the billing subject | You issue it to registered crawler operators → crawler sends it → ESA relays it | 402 invalid_token |
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:
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
| Header | Requirement | Description |
|---|---|---|
Authorization: Bearer <gateway_key> | Required | Gateway-layer authentication (§4). |
X-Crawler-Token | Recommended | Crawler identity token, relayed from the crawler's original request. Either this header or body crawlerToken; header wins when both present. |
X-Original-Crawler-Agent | Recommended | The original crawler UA, always sent redundantly (§5.3 dual channel). |
X-Request-Id | Optional | Correlation id. Echo it back in the response header; generate one yourself if absent (§14.3). |
5.2 Request body fields
| Field | Type | Requirement | Description |
|---|---|---|---|
publisherDomain | string | Required | Domain 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. |
resourceUrl | string | Required | Path of the visited resource (e.g. /premium/report.html) or a full URL. Used for URL-rule matching. |
crawlerToken | string | Optional* | 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). |
crawlerAgent | string | Recommended | Body channel for crawler identity: if ESA already identified the crawler → the normalized identifier (e.g. "gptbot"); otherwise → the raw UA string (§5.3). |
crawlerAutoPrice | boolean | Pick one | Auto-charge (recommended; covers the vast majority of traffic in phase 1): charge the resource price directly when balance suffices. |
crawlerExactPrice | string | Exact price: format "USD 0.10"; charge only when it exactly matches the resource price. | |
crawlerMaxPrice | string | Budget cap: format "USD 0.50"; charge only when resource price ≤ cap. | |
metadata | object | Optional | Pass-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:
| Channel | Field | Content | Purpose |
|---|---|---|---|
| Body field | crawlerAgent | Identified → normalized identifier; not identified → raw UA | Fast path: direct hit on known crawler types |
| HTTP header | X-Original-Crawler-Agent | Always the raw crawler UA string | Audit 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
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:
// 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
| Field | Type | Requirement | Description |
|---|---|---|---|
accessGranted | boolean | Required | The unified allow/deny flag (§6.1). |
statusCode | number | Recommended | HTTP status to return to the crawler (200 / 402 / 403 …); also use it as the verify response's own HTTP status. |
message | string | Recommended | One human- (and LLM-) readable sentence describing the outcome. |
error | string | Required if non-200 | Machine-readable error code (§8). |
requiresPayment | boolean | 402: recommended | Marks "payment unlocks this" (vs. 403's "payment cannot unlock"). |
price / currency | number / string | Discovery: required | Resource price and ISO 4217 currency (e.g. USD). Include in every 402 when possible so crawlers can budget. |
charged / chargeAmount | boolean / number | On charge | Whether and how much this request was charged. ESA may map these to response headers (e.g. X-Charged / X-Charge-Amount). |
transaction | object | Optional | Transaction receipt {transactionId, timestamp} for audit and disputes. |
crawler | object | Optional | Crawler account snapshot, e.g. {balance, required, shortfall} — telling a broke crawler exactly how much it is short is a conversion-critical detail. |
rule | object | Optional | Matched-rule summary {urlPattern, accessMode, price, billingType} — helps crawlers understand the billing basis. |
crawlerIdentification | object | Recommended | Identification result (§6.3). Include in every response, including 402/403. |
onboarding | string (URL) | 402: required | Registration / top-up guide URL (§9). |
guidance | object | Strongly recommended | Machine-readable guidance block (§9.2) — the deciding factor for cold-start conversion. |
timestamp | string | Recommended | ISO 8601 processing time. |
6.3 crawlerIdentification
"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)
{
"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:
| Mode | Request parameter | Behavior | Typical use |
|---|---|---|---|
| Auto-charge Recommended | crawlerAutoPrice: true | Charge the resource price directly when balance suffices — payment completes in one request | Automated 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/currency | First-time price exploration, budgeting |
| Exact price | crawlerExactPrice: "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 cap | crawlerMaxPrice: "USD 0.50" | Charge the actual price when resource price ≤ cap, else 402 price_too_high | Cost control, overspend protection |
// 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 }
INVALID_PARAMETER when combined); malformed price strings or unsupported currencies
return 400 INVALID_PRICE_FORMAT.8Status & Error Codes
8.1 HTTP status codes
| Status | Meaning | accessGranted | ESA behavior |
|---|---|---|---|
| 200 | Allow (free / charge succeeded / default allow) | true | Fetch origin |
| 400 | Parameter error (missing field / exclusive conflict / bad price format) | false | Relay |
| 401 | Gateway auth failed (gateway_key missing / invalid) | false | Prompt the user to check the console key |
| 402 | Payment-side action required (see error) | false | Relay the 402 JSON verbatim to the crawler |
| 403 | Publisher block — payment cannot unlock | false | Relay the 403 JSON verbatim to the crawler |
| 500 | Provider internal error | false | Per timeout / failure policy (§14.2) |
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)
| Status | error | Meaning | Correct crawler reaction |
|---|---|---|---|
| 402 | invalid_token | Token missing / invalid / account disabled | Register per onboarding / guidance |
| 402 | insufficient_balance | Balance < price (include crawler.shortfall) | Top up, then retry |
| 402 | price_required | Paid resource but no price intent (discovery) | Read price, retry with a price intent |
| 402 | price_mismatch | Exact-mode price differs | Retry with the real price from the response |
| 402 | price_too_high | Max-mode budget below price | Raise budget or skip the resource |
| 403 | access_blocked | Publisher block rule matched | Skip this URL; do not register / top up for it |
| 401 | gateway_unauthorized | gateway_key missing / invalid | (Publisher / ops) check the console key |
| 400 | MISSING_REQUIRED_FIELD / INVALID_PARAMETER / INVALID_PRICE_FORMAT | Parameter problems | Fix the request |
| 500 | INTERNAL_ERROR | Provider internal fault | Retry later |
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):
"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:
{
"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 subfield | Purpose |
|---|---|
onboarding / docs / llmsTxt | Absolute URLs of guidance resources. llms.txt is a plain-text site explainer for LLMs (community convention) — recommended. |
registerApi | One-line "method + URL + fields" description of the register API — crawlers can construct the call directly. |
steps | Numbered steps: register → log in → top up → retry this exact URL with the token. Following them just works. |
signupBonus | Dynamic field: present only when trial credit is enabled (§10.2) — tells the crawler it can try immediately after registering. |
agentAdvice | Behavioral 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)
- 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.
- All links absolute, derived from server-side configuration — never rely on crawlers guessing your domain.
- 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".
- 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:
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 field | Description |
|---|---|
urlPattern | URL matching pattern; support exact / prefix / regex types |
accessMode | allow (free) / pay (charge) / block (deny; payment cannot unlock) |
price / currency | Pricing for pay mode. Extensible to per-crawler-type differential pricing (e.g. training vs. search crawlers) |
billingType | Billing type: start with per-request (most common); time passes / subscriptions can come later |
priority | Matching 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
| Capability | Key points |
|---|---|
| Register / login API | Offer 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 credentials | Issue 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 query | Provide a read-only balance endpoint callable with the crawler token itself — crawlers check funds mid-crawl without switching credentials. |
| Online top-up | A 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
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.
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:
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
| Parameter | What it is | Who provides it / where to get it | Example |
|---|---|---|---|
| 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)
- Implement and deploy the verify API (the §5–§8 contract) → this yields your Endpoint;
- The publisher signs up on your platform (registers, configures rules and pricing) → you issue their Gateway Key;
- 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.
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:
- 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;
- 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 form | What the publisher fills in | Precondition |
|---|---|---|
| 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":
{
"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"
}
}| Field | Description |
|---|---|
provider | Provider identifier (display name and brand assets submitted separately). |
verify_endpoint | Verify API address, auto-filled in the console (user-editable for test environments). |
auth | Gateway-layer auth scheme. Currently bearer; key_field names the key the console asks users for. |
headers / request_body / response | Header 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).
13Integration Checklist & Acceptance Test Matrix
13.1 Checklist
| # | Item | Level |
|---|---|---|
| 1 | Provide an HTTPS POST verify API that returns JSON with accessGranted in every case | MUST |
| 2 | Validate Authorization: Bearer <gateway_key>; fail with 401 gateway_unauthorized; support concurrent multi-key rotation | MUST |
| 3 | Take crawler identity from the X-Crawler-Token header / body and bill it; never hard-reject token-less requests — route them to 402 onboarding | MUST |
| 4 | Support crawlerAutoPrice auto-charging; carry onboarding in 402 responses | MUST |
| 5 | Return 403 access_blocked for block scenarios, strictly separated from 402 semantics | MUST |
| 6 | Follow the §8.2 error codes; charge atomically with no overdraft (§10.3) | MUST |
| 7 | Support discovery / exact / max price intents; return crawlerIdentification; echo X-Request-Id | SHOULD |
| 8 | Embed the guidance block in 402/403 (§9.2); offer pure-API registration and balance query | SHOULD |
| 9 | Provide a test Endpoint / keys to ESA for joint acceptance; submit a Provider Profile (§12) only if you want the console dropdown | MUST |
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:
| # | Status | error | Trigger condition | How to construct |
|---|---|---|---|---|
| 1 | 200 | — | Allow rule matched | Verify a path configured as allow |
| 2 | 200 | — | No rule matched (default allow) | Verify a path with no rule |
| 3 | 200 | — | Paid charge succeeded | Pay path + crawlerAutoPrice:true + funded account; assert exact balance decrease |
| 4 | 402 | invalid_token | Token missing / invalid / disabled | Hit a pay path without X-Crawler-Token; assert onboarding present |
| 5 | 402 | insufficient_balance | Balance < price | Zero-balance account on a pay path; assert crawler.shortfall |
| 6 | 402 | price_required | Pay path without price intent | Assert price / currency returned |
| 7 | 402 | price_mismatch | Exact price differs from list price | crawlerExactPrice with a wrong price |
| 8 | 402 | price_too_high | Max budget < list price | crawlerMaxPrice below the price |
| 9 | 400 | INVALID_PRICE_FORMAT | Malformed price string | crawlerExactPrice: "notaprice" |
| 10 | 403 | access_blocked | Block rule matched | Verify a blocked path (must 403 even with a valid token) |
| 11 | 401 | gateway_unauthorized | Gateway key missing / wrong | Wrong Authorization: Bearer value |
| 12 | 500 | INTERNAL_ERROR | Internal fault | Not stably triggerable black-box; confirm the fallback path by code review (documented coverage) |
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_ERRORwithaccessGranted:false— never hang the connection; - Keep 5xx
messages generic; internals go to your own logs only.
14.3 X-Request-Id & reconciliation
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
- Self-test all 12 return values of the §13.2 matrix in your test environment;
- 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;
- Canary: enable on a test site first; watch latency / verdict distribution / reconciliation consistency;
- Enter the console provider list for general availability.