{
  "@context": "https://agentflare.org/schema",
  "type": "Article",
  "tier": "L2-full",
  "title": "How the Edge Verify API Decides Access",
  "description": "The edge gateway calls POST /api/v1/access/verify before fetching from origin and reads one field, accessGranted. On true it fetches the content; on false…",
  "canonical": "https://agentflare.org/guides/how-the-edge-verify-api-decides-access.html",
  "category": "guides",
  "updated": "2026-10-16",
  "generated_at": "2026-10-16T00:37:01.652Z",
  "facts": [
    {
      "label": "Track",
      "value": "For publishers"
    },
    {
      "label": "Level",
      "value": "Intermediate"
    },
    {
      "label": "Access",
      "value": "Free / open"
    },
    {
      "label": "Updated",
      "value": "2026-10-16"
    }
  ],
  "data": {
    "topic": "How the Edge Verify API Decides Access",
    "track": "publisher",
    "access": "free",
    "generated_at": "2026-10-16T00:37:01.652Z",
    "difficulty": "Intermediate",
    "tldr": "The edge gateway calls POST /api/v1/access/verify before fetching from origin and reads one field, accessGranted. On true it fetches the content; on false it returns the 402 or 403 JSON to the crawler unchanged.",
    "intro": "This guide is for publishers and edge-gateway integrators who want AgentFlare (AISA) to decide per request whether a crawler gets free access, must pay, or is blocked. You will see exactly what the gateway sends to the verify endpoint, which decisions can come back, what the gateway does with each one, and how a refusal teaches a new crawler to register, top up and retry. A table of verify outcomes closes the guide.",
    "prerequisites": [
      "A gateway token issued by AISA, delivered out of band",
      "A publisher account with URL rules (allow, pay, block) configured",
      "Python with the requests library for the examples"
    ],
    "steps": [
      {
        "title": "Send the verify call before origin fetch",
        "body": "When a crawler hits a protected URL, the gateway makes an out-of-band POST to /api/v1/access/verify. Put the gateway token in Authorization: Bearer. Forward the crawler's token in X-Crawler-Token (it replaces the earlier name X-AISA-Crawler-Token, which older AISA pages still show). Pass the original crawler user agent too, because gateways rewrite User-Agent. The test host is https://esa.aisa.one; production is https://cdn.aisa.one.",
        "code": {
          "lang": "python",
          "text": "import requests\n\nresp = requests.post(\n    'https://esa.aisa.one/api/v1/access/verify',\n    headers={\n        'Authorization': 'Bearer YOUR_GATEWAY_TOKEN',\n        'X-Crawler-Token': 'YOUR_CRAWLER_TOKEN',\n        'X-ESA-Original-Crawler-Agent': 'GPTBot/1.0',\n        'Content-Type': 'application/json',\n    },\n    json={\n        'publisherDomain': 'PUBLISHER-SITE',\n        'resourceUrl': '/premium/article.html',\n        'crawlerAutoPrice': True,\n        'crawlerAgent': 'gptbot',\n    },\n    timeout=10,\n)\nprint(resp.status_code, resp.json())"
        },
        "sources": [
          "S1",
          "S3",
          "S4"
        ]
      },
      {
        "title": "Read accessGranted and act on it",
        "body": "A minimal integration reads one field. When accessGranted is true, serve the content by fetching from origin or cache. When it is false, do not serve anything; return statusCode and the response body to the crawler. The verify endpoint is a pure decision API: it returns JSON only and never page content, so your content stays with the gateway and origin.",
        "code": {
          "lang": "python",
          "text": "def handle(verdict):\n    if verdict['accessGranted']:\n        return fetch_from_origin()  # your own origin or cache fetch\n    # denied: pass status and JSON body through unchanged\n    return respond(verdict['statusCode'], verdict)"
        },
        "sources": [
          "S2",
          "S3"
        ]
      },
      {
        "title": "Know how the rules decide",
        "body": "Publisher rules are first-match-wins, and URL query strings are stripped before matching. A block rule returns 403 access_blocked whatever the token or balance. An allow rule returns 200 free with no token needed. A pay rule needs a valid crawler token and, with crawlerAutoPrice set to true and enough balance, returns 200 plus a charge. If no rule matches, the result is 200 default allow with no charge. Billing is per request.",
        "code": null,
        "sources": [
          "S3"
        ]
      },
      {
        "title": "Pass 402 and 403 bodies through untouched",
        "body": "On denial, deliver the verify JSON to the crawler unmodified. A 402 body carries onboarding and a guidance block that tell a new crawler how to register, fund a balance (possibly with a signup trial credit) and retry the same URL with X-Crawler-Token. A 402 means payment can unlock the page; a 403 means the publisher blocked it and paying will not help. Never rewrite one into the other.",
        "code": null,
        "sources": [
          "S2",
          "S3"
        ]
      },
      {
        "title": "Forward the price intent correctly",
        "body": "Price intent travels in the request body. The three fields crawlerAutoPrice, crawlerExactPrice (for example USD 0.10) and crawlerMaxPrice (for example USD 0.50) are mutually exclusive, so send at most one. Sending none triggers price discovery: no charge, and a 402 price_required response with price and currency. An exact price that differs returns 402 price_mismatch; a maximum below the resource price returns 402 price_too_high.",
        "code": null,
        "sources": [
          "S2",
          "S4"
        ]
      },
      {
        "title": "Separate gateway failures from crawler failures",
        "body": "The two auth layers are independent. A missing or invalid gateway token returns 401 gateway_unauthorized, which is for the gateway operator to fix, not the crawler. A missing or invalid crawler token returns 402 invalid_token instead, because registering solves it. A bad price string returns 400 invalid_price_format, and a provider fault returns 500 internal_error. Reuse the same optional X-Request-Id on retries so both sides can reconcile charges.",
        "code": null,
        "sources": [
          "S2",
          "S3",
          "S4"
        ]
      }
    ],
    "reference": {
      "title": "Verify outcomes and gateway action",
      "columns": [
        "Situation",
        "Status and error",
        "accessGranted",
        "Gateway action"
      ],
      "rows": [
        [
          "Allow rule, or no rule matched (default allow)",
          "200, no charge",
          "true",
          "Fetch from origin and serve the content"
        ],
        [
          "Pay rule, valid token, enough balance",
          "200 with charged and chargeAmount",
          "true",
          "Fetch from origin and serve the content"
        ],
        [
          "Pay rule, token missing, invalid or account inactive",
          "402 invalid_token",
          "false",
          "Pass the JSON through; crawler follows onboarding and guidance"
        ],
        [
          "Pay rule, balance too low",
          "402 insufficient_balance (crawler.shortfall)",
          "false",
          "Pass the JSON through; crawler tops up and retries"
        ],
        [
          "Pay rule, no price intent sent",
          "402 price_required (price, currency)",
          "false",
          "Pass the JSON through; crawler retries with a price intent"
        ],
        [
          "Exact or maximum price check fails",
          "402 price_mismatch or price_too_high",
          "false",
          "Pass the JSON through; crawler corrects its price or gives up"
        ],
        [
          "Block rule matched",
          "403 access_blocked",
          "false",
          "Pass the JSON through; paying cannot unlock it"
        ],
        [
          "Bad request, bad gateway key, provider fault",
          "400 invalid_price_format, 401 gateway_unauthorized, 500 internal_error",
          "false",
          "Fix the request or console key; apply your timeout and fault policy for 500"
        ]
      ],
      "sources": [
        "S2",
        "S3",
        "S4"
      ]
    },
    "pitfalls": [
      {
        "text": "Never send the crawler token in Authorization: Bearer; that slot is the gateway layer, and the call fails with 401 gateway_unauthorized.",
        "source": "S1"
      },
      {
        "text": "Do not mix 402 and 403. A block rule returns 403 even if the crawler pays, so its guidance should say not to register for that URL.",
        "source": "S2"
      },
      {
        "text": "Combining crawlerAutoPrice, crawlerExactPrice or crawlerMaxPrice in one request is invalid; the three price fields are mutually exclusive.",
        "source": "S4"
      },
      {
        "text": "The original user agent header is spelled X-Original-Crawler-Agent in the Open Verify specification and X-ESA-Original-Crawler-Agent in the AISA endpoint docs; match the endpoint you call.",
        "source": "S3"
      }
    ],
    "takeaways": [
      "The gateway enforces; AISA decides. One boolean, accessGranted, controls whether content is fetched from origin.",
      "Send the crawler token in X-Crawler-Token and keep Authorization: Bearer for the gateway token only.",
      "Forward 402 and 403 JSON unmodified, since its onboarding and guidance fields convert new crawlers."
    ],
    "faq": [
      {
        "q": "What does the gateway send to POST /api/v1/access/verify?",
        "a": "Headers Authorization: Bearer with the gateway token, X-Crawler-Token and the original crawler agent header. The JSON body carries publisherDomain, resourceUrl, a price intent such as crawlerAutoPrice, and crawlerAgent."
      },
      {
        "q": "What happens when a crawler sends no token?",
        "a": "The request still goes through normal decisions. Free or default-allowed content returns 200; paid content returns 402 invalid_token with onboarding and guidance so the crawler can register and retry."
      },
      {
        "q": "Does the verify API return the page content?",
        "a": "No. It returns only a JSON decision. After accessGranted is true, the gateway fetches the content from origin or cache itself."
      },
      {
        "q": "How do I test all the response types?",
        "a": "AISA lists 12 return values and a test matrix at /cdn/cdntest.html. A GET on /api/v1/access/verify returns a machine-readable description of all parameters and codes."
      }
    ],
    "sources": [
      {
        "title": "Header name: X-Crawler-Token",
        "url": "https://esa.aisa.one/cdn/open-verify.html",
        "domain": "esa.aisa.one"
      },
      {
        "title": "Open Verify specification (edge gateway ↔ verify provider)",
        "url": "https://esa.aisa.one/cdn/open-verify.html",
        "domain": "esa.aisa.one"
      },
      {
        "title": "AISA verify API for publishers and edge gateways (llms.txt)",
        "url": "https://esa.aisa.one/cdn/llms.txt",
        "domain": "esa.aisa.one"
      },
      {
        "title": "AISA publisher API documentation",
        "url": "https://esa.aisa.one/cdn/docs.html",
        "domain": "esa.aisa.one"
      }
    ]
  },
  "analysis_md": "The edge gateway calls POST /api/v1/access/verify before fetching from origin and reads one field, accessGranted. On true it fetches the content; on false it returns the 402 or 403 JSON to the crawler unchanged.\n\nThis guide is for publishers and edge-gateway integrators who want AgentFlare (AISA) to decide per request whether a crawler gets free access, must pay, or is blocked. You will see exactly what the gateway sends to the verify endpoint, which decisions can come back, what the gateway does with each one, and how a refusal teaches a new crawler to register, top up and retry. A table of verify outcomes closes the guide.\n\n## Before you start\n\n- A gateway token issued by AISA, delivered out of band\n- A publisher account with URL rules (allow, pay, block) configured\n- Python with the requests library for the examples\n\n## Step by step\n\n### 1. Send the verify call before origin fetch\n\nWhen a crawler hits a protected URL, the gateway makes an out-of-band POST to /api/v1/access/verify. Put the gateway token in Authorization: Bearer. Forward the crawler's token in X-Crawler-Token (it replaces the earlier name X-AISA-Crawler-Token, which older AISA pages still show). Pass the original crawler user agent too, because gateways rewrite User-Agent. The test host is https://esa.aisa.one; production is https://cdn.aisa.one.\n\n```python\nimport requests\n\nresp = requests.post(\n    'https://esa.aisa.one/api/v1/access/verify',\n    headers={\n        'Authorization': 'Bearer YOUR_GATEWAY_TOKEN',\n        'X-Crawler-Token': 'YOUR_CRAWLER_TOKEN',\n        'X-ESA-Original-Crawler-Agent': 'GPTBot/1.0',\n        'Content-Type': 'application/json',\n    },\n    json={\n        'publisherDomain': 'PUBLISHER-SITE',\n        'resourceUrl': '/premium/article.html',\n        'crawlerAutoPrice': True,\n        'crawlerAgent': 'gptbot',\n    },\n    timeout=10,\n)\nprint(resp.status_code, resp.json())\n```\n\n### 2. Read accessGranted and act on it\n\nA minimal integration reads one field. When accessGranted is true, serve the content by fetching from origin or cache. When it is false, do not serve anything; return statusCode and the response body to the crawler. The verify endpoint is a pure decision API: it returns JSON only and never page content, so your content stays with the gateway and origin.\n\n```python\ndef handle(verdict):\n    if verdict['accessGranted']:\n        return fetch_from_origin()  # your own origin or cache fetch\n    # denied: pass status and JSON body through unchanged\n    return respond(verdict['statusCode'], verdict)\n```\n\n### 3. Know how the rules decide\n\nPublisher rules are first-match-wins, and URL query strings are stripped before matching. A block rule returns 403 access_blocked whatever the token or balance. An allow rule returns 200 free with no token needed. A pay rule needs a valid crawler token and, with crawlerAutoPrice set to true and enough balance, returns 200 plus a charge. If no rule matches, the result is 200 default allow with no charge. Billing is per request.\n\n### 4. Pass 402 and 403 bodies through untouched\n\nOn denial, deliver the verify JSON to the crawler unmodified. A 402 body carries onboarding and a guidance block that tell a new crawler how to register, fund a balance (possibly with a signup trial credit) and retry the same URL with X-Crawler-Token. A 402 means payment can unlock the page; a 403 means the publisher blocked it and paying will not help. Never rewrite one into the other.\n\n### 5. Forward the price intent correctly\n\nPrice intent travels in the request body. The three fields crawlerAutoPrice, crawlerExactPrice (for example USD 0.10) and crawlerMaxPrice (for example USD 0.50) are mutually exclusive, so send at most one. Sending none triggers price discovery: no charge, and a 402 price_required response with price and currency. An exact price that differs returns 402 price_mismatch; a maximum below the resource price returns 402 price_too_high.\n\n### 6. Separate gateway failures from crawler failures\n\nThe two auth layers are independent. A missing or invalid gateway token returns 401 gateway_unauthorized, which is for the gateway operator to fix, not the crawler. A missing or invalid crawler token returns 402 invalid_token instead, because registering solves it. A bad price string returns 400 invalid_price_format, and a provider fault returns 500 internal_error. Reuse the same optional X-Request-Id on retries so both sides can reconcile charges.\n\n## Verify outcomes and gateway action\n\n| Situation | Status and error | accessGranted | Gateway action |\n| --- | --- | --- | --- |\n| Allow rule, or no rule matched (default allow) | 200, no charge | true | Fetch from origin and serve the content |\n| Pay rule, valid token, enough balance | 200 with charged and chargeAmount | true | Fetch from origin and serve the content |\n| Pay rule, token missing, invalid or account inactive | 402 invalid_token | false | Pass the JSON through; crawler follows onboarding and guidance |\n| Pay rule, balance too low | 402 insufficient_balance (crawler.shortfall) | false | Pass the JSON through; crawler tops up and retries |\n| Pay rule, no price intent sent | 402 price_required (price, currency) | false | Pass the JSON through; crawler retries with a price intent |\n| Exact or maximum price check fails | 402 price_mismatch or price_too_high | false | Pass the JSON through; crawler corrects its price or gives up |\n| Block rule matched | 403 access_blocked | false | Pass the JSON through; paying cannot unlock it |\n| Bad request, bad gateway key, provider fault | 400 invalid_price_format, 401 gateway_unauthorized, 500 internal_error | false | Fix the request or console key; apply your timeout and fault policy for 500 |\n\n## Common pitfalls\n\n- Never send the crawler token in Authorization: Bearer; that slot is the gateway layer, and the call fails with 401 gateway_unauthorized.\n- Do not mix 402 and 403. A block rule returns 403 even if the crawler pays, so its guidance should say not to register for that URL.\n- Combining crawlerAutoPrice, crawlerExactPrice or crawlerMaxPrice in one request is invalid; the three price fields are mutually exclusive.\n- The original user agent header is spelled X-Original-Crawler-Agent in the Open Verify specification and X-ESA-Original-Crawler-Agent in the AISA endpoint docs; match the endpoint you call.\n\n## Key takeaways\n\n- The gateway enforces; AISA decides. One boolean, accessGranted, controls whether content is fetched from origin.\n- Send the crawler token in X-Crawler-Token and keep Authorization: Bearer for the gateway token only.\n- Forward 402 and 403 JSON unmodified, since its onboarding and guidance fields convert new crawlers.\n\n## FAQ\n\n**What does the gateway send to POST /api/v1/access/verify?**\nHeaders Authorization: Bearer with the gateway token, X-Crawler-Token and the original crawler agent header. The JSON body carries publisherDomain, resourceUrl, a price intent such as crawlerAutoPrice, and crawlerAgent.\n\n**What happens when a crawler sends no token?**\nThe request still goes through normal decisions. Free or default-allowed content returns 200; paid content returns 402 invalid_token with onboarding and guidance so the crawler can register and retry.\n\n**Does the verify API return the page content?**\nNo. It returns only a JSON decision. After accessGranted is true, the gateway fetches the content from origin or cache itself.\n\n**How do I test all the response types?**\nAISA lists 12 return values and a test matrix at /cdn/cdntest.html. A GET on /api/v1/access/verify returns a machine-readable description of all parameters and codes.\n",
  "sources": [
    {
      "title": "Header name: X-Crawler-Token",
      "url": "https://esa.aisa.one/cdn/open-verify.html"
    },
    {
      "title": "Open Verify specification (edge gateway ↔ verify provider)",
      "url": "https://esa.aisa.one/cdn/open-verify.html"
    },
    {
      "title": "AISA verify API for publishers and edge gateways (llms.txt)",
      "url": "https://esa.aisa.one/cdn/llms.txt"
    },
    {
      "title": "AISA publisher API documentation",
      "url": "https://esa.aisa.one/cdn/docs.html"
    }
  ],
  "related": [
    {
      "name": "Pricing Content by Directory: Best Practices",
      "url": "https://agentflare.org/guides/pricing-content-by-directory-best-practices.html"
    },
    {
      "name": "robots.txt, llms.txt & Sitemaps for AI Crawlers",
      "url": "https://agentflare.org/guides/robotstxt-llmstxt-sitemaps-for-ai-crawlers.html"
    },
    {
      "name": "Getting Started with HTTP 402 Pay-Per-Crawl",
      "url": "https://agentflare.org/guides/getting-started-with-http-402-pay-per-crawl.html"
    },
    {
      "name": "Crawler Tokens & Auto-Pay: A How-To",
      "url": "https://agentflare.org/guides/crawler-tokens-auto-pay-a-how-to.html"
    },
    {
      "name": "Handling 402 and 403 Responses: An Error Reference for Agents",
      "url": "https://agentflare.org/guides/handling-402-and-403-responses-an-error-reference-for-agents.html"
    },
    {
      "name": "Budgeting a Paid Crawl: Discover, Survey, Then Buy",
      "url": "https://agentflare.org/guides/budgeting-a-paid-crawl-discover-survey-then-buy.html"
    }
  ],
  "pricing": {
    "price_usd": 0,
    "method": "402",
    "endpoint": "https://esa.aisa.one/api/v1/access/verify",
    "autopay_hint": "set crawlerAutoPrice=true with the X-Crawler-Token header",
    "onboarding": "https://esa.aisa.one/agent/guide.html"
  },
  "powered_by": "AISA — agent-native search, settlement & delivery (https://aisa.one)"
}