{
  "@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. Denials come back as 402 or 403 JSON that…",
  "canonical": "https://agentflare.org/guides/how-the-edge-verify-api-decides-access.html",
  "category": "guides",
  "updated": "2026-10-30",
  "generated_at": "2026-10-30T00:37:02.265Z",
  "facts": [
    {
      "label": "Track",
      "value": "For publishers"
    },
    {
      "label": "Level",
      "value": "Intermediate"
    },
    {
      "label": "Access",
      "value": "Free / open"
    },
    {
      "label": "Updated",
      "value": "2026-10-30"
    }
  ],
  "data": {
    "topic": "How the Edge Verify API Decides Access",
    "track": "publisher",
    "access": "free",
    "generated_at": "2026-10-30T00:37:02.265Z",
    "difficulty": "Intermediate",
    "tldr": "The edge gateway calls POST /api/v1/access/verify before fetching from origin and reads one field, accessGranted. Denials come back as 402 or 403 JSON that the gateway passes to the crawler unchanged.",
    "intro": "This guide is for publishers and CDN or ESA integrators who want to know exactly what an edge gateway sends to the AISA verify endpoint and what happens next. You will be able to build the verify request, read the decision, and return the right response to the crawler. You will also be able to tell free, paid and blocked outcomes apart by status code and error value, and to see why the 402 body must reach the crawler untouched. The guide ends with a table of every verify outcome.",
    "prerequisites": [
      "A gateway token issued by AISA out of band",
      "Publisher rules configured as allow, pay or block",
      "An edge gateway that can make server-side HTTPS calls"
    ],
    "steps": [
      {
        "title": "Send the verify call before origin fetch",
        "body": "When a crawler request hits the edge, call POST /api/v1/access/verify before going to origin. Put the gateway token in Authorization: Bearer. Forward the crawler's own token in X-Crawler-Token, which replaces the earlier name X-AISA-Crawler-Token still shown on older AISA pages. Always forward the original crawler user agent, because gateways rewrite User-Agent. X-Request-Id is optional and is echoed back for reconciliation.",
        "code": {
          "lang": "http",
          "text": "POST /api/v1/access/verify HTTP/1.1\nHost: cdn.aisa.one\nAuthorization: Bearer YOUR_GATEWAY_TOKEN\nX-Crawler-Token: YOUR_CRAWLER_TOKEN\nX-ESA-Original-Crawler-Agent: ORIGINAL-CRAWLER-UA\nX-Request-Id: req-8c85ba19abef566b\nContent-Type: application/json"
        },
        "sources": [
          "S1",
          "S3",
          "S4"
        ]
      },
      {
        "title": "Build the JSON request body",
        "body": "Send publisherDomain (the request Host) and resourceUrl (the path; query strings are stripped before rule matching). Add crawlerAgent: the identified bot id, or the original user agent if unidentified. Add crawlerAutoPrice set to true so a paid resource is charged in one call when the balance allows. crawlerAutoPrice, crawlerExactPrice and crawlerMaxPrice are mutually exclusive; sending none means price discovery. Save these fields as verify-body.json.",
        "code": {
          "lang": "bash",
          "text": "# verify-body.json holds: publisherDomain, resourceUrl,\n# crawlerAutoPrice (true) and crawlerAgent\ncurl -X POST https://cdn.aisa.one/api/v1/access/verify \\\n  -H 'Authorization: Bearer YOUR_GATEWAY_TOKEN' \\\n  -H 'X-Crawler-Token: YOUR_CRAWLER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  --data @verify-body.json"
        },
        "sources": [
          "S2",
          "S3",
          "S4"
        ]
      },
      {
        "title": "Read accessGranted and decide",
        "body": "A minimal gateway reads one field. When accessGranted is true, fetch the content from origin or cache and serve it with status 200. When it is false, do not serve content. Rules are first-match-wins: block gives 403 access_blocked, allow gives 200 free, pay needs a valid crawler token, and no matching rule gives 200 default allow with no charge. Charged responses carry charged and chargeAmount.",
        "code": null,
        "sources": [
          "S2",
          "S3"
        ]
      },
      {
        "title": "Pass 402 and 403 bodies through unchanged",
        "body": "On accessGranted false with statusCode 402 or 403, return statusCode and the verify JSON body to the crawler exactly as received. The body carries onboarding and a guidance block that teach a new crawler to register, fund a balance and retry with X-Crawler-Token. A 402 means payment can unlock the resource; a 403 access_blocked means payment will not help. Render your own block page if you want one, since AISA returns JSON only.",
        "code": null,
        "sources": [
          "S2",
          "S3"
        ]
      },
      {
        "title": "Handle gateway-side failures separately",
        "body": "A 401 gateway_unauthorized means the gateway token is missing or invalid, so check the key configured in the console; this is an operator error, not a crawler one. A 400 means a parameter problem such as invalid_price_format or conflicting price fields. A 500 internal_error is a provider fault, handled by your timeout and fault policy. On timeout retries, reuse the same X-Request-Id 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 returns to crawler"
      ],
      "rows": [
        [
          "allow rule, or no rule matched (default allow)",
          "200, no charge",
          "true",
          "Content from origin, 200"
        ],
        [
          "pay rule, valid token, enough balance, crawlerAutoPrice true",
          "200 with charged and chargeAmount",
          "true",
          "Content from origin, 200"
        ],
        [
          "pay rule, token missing or invalid",
          "402 invalid_token",
          "false",
          "402 JSON passed through, with onboarding and guidance"
        ],
        [
          "pay rule, balance too low",
          "402 insufficient_balance, crawler.shortfall",
          "false",
          "402 JSON passed through, top up then retry"
        ],
        [
          "pay rule, no price intent (price discovery)",
          "402 price_required, price and currency",
          "false",
          "402 JSON passed through with the price quote"
        ],
        [
          "exact or max price intent does not fit",
          "402 price_mismatch or price_too_high",
          "false",
          "402 JSON passed through, retry with the real price or a higher budget"
        ],
        [
          "block rule matched",
          "403 access_blocked",
          "false",
          "403 JSON passed through, payment cannot unlock"
        ],
        [
          "gateway or request errors",
          "401 gateway_unauthorized, 400 invalid_price_format, 500 internal_error",
          "false",
          "401: check the gateway token in the console; 400: fix the request; 500: apply your fault policy"
        ]
      ],
      "sources": [
        "S2",
        "S3"
      ]
    },
    "pitfalls": [
      {
        "text": "Never send the crawler token in Authorization: Bearer; that header is reserved for the gateway layer and yields 401 gateway_unauthorized.",
        "source": "S1"
      },
      {
        "text": "Do not send crawlerAutoPrice, crawlerExactPrice and crawlerMaxPrice together; they are mutually exclusive and a conflict returns a 400.",
        "source": "S2"
      },
      {
        "text": "Do not rewrite or trim the 402 JSON. Its onboarding and guidance fields are how new crawlers learn to register and pay.",
        "source": "S3"
      },
      {
        "text": "The original-agent header is spelled X-ESA-Original-Crawler-Agent in AISA docs and X-Original-Crawler-Agent in the Open Verify spec; the body field crawlerAgent carries the same data.",
        "source": "S2"
      }
    ],
    "takeaways": [
      "Read accessGranted: true means fetch from origin, false means return statusCode and the JSON body to the crawler.",
      "402 means payment can unlock the resource; 403 access_blocked means it cannot; 401 is reserved for the gateway layer.",
      "Send the crawler token in X-Crawler-Token and the gateway token in Authorization: Bearer, never mixed."
    ],
    "faq": [
      {
        "q": "What does the gateway send to POST /api/v1/access/verify?",
        "a": "Authorization: Bearer with the gateway token, X-Crawler-Token forwarded from the crawler, the original crawler user agent header, and a JSON body with publisherDomain, resourceUrl, crawlerAutoPrice and crawlerAgent. X-Request-Id is optional."
      },
      {
        "q": "What happens when the crawler has no token?",
        "a": "On a pay rule, verify returns 402 with error invalid_token, plus onboarding and guidance. The gateway passes that JSON to the crawler so it can register and retry. On an allow rule, no token is needed and the response is 200."
      },
      {
        "q": "What is the difference between 402 and 403?",
        "a": "402 means the resource is for sale and payment or registration can unlock it. 403 with access_blocked means the publisher blocked it, regardless of token or balance, so the crawler should not register or top up for that URL."
      },
      {
        "q": "How do I get price discovery without being charged?",
        "a": "Send none of crawlerAutoPrice, crawlerExactPrice or crawlerMaxPrice. For a pay resource, verify returns 402 with error price_required plus price and currency, and does not charge."
      }
    ],
    "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. Denials come back as 402 or 403 JSON that the gateway passes to the crawler unchanged.\n\nThis guide is for publishers and CDN or ESA integrators who want to know exactly what an edge gateway sends to the AISA verify endpoint and what happens next. You will be able to build the verify request, read the decision, and return the right response to the crawler. You will also be able to tell free, paid and blocked outcomes apart by status code and error value, and to see why the 402 body must reach the crawler untouched. The guide ends with a table of every verify outcome.\n\n## Before you start\n\n- A gateway token issued by AISA out of band\n- Publisher rules configured as allow, pay or block\n- An edge gateway that can make server-side HTTPS calls\n\n## Step by step\n\n### 1. Send the verify call before origin fetch\n\nWhen a crawler request hits the edge, call POST /api/v1/access/verify before going to origin. Put the gateway token in Authorization: Bearer. Forward the crawler's own token in X-Crawler-Token, which replaces the earlier name X-AISA-Crawler-Token still shown on older AISA pages. Always forward the original crawler user agent, because gateways rewrite User-Agent. X-Request-Id is optional and is echoed back for reconciliation.\n\n```http\nPOST /api/v1/access/verify HTTP/1.1\nHost: cdn.aisa.one\nAuthorization: Bearer YOUR_GATEWAY_TOKEN\nX-Crawler-Token: YOUR_CRAWLER_TOKEN\nX-ESA-Original-Crawler-Agent: ORIGINAL-CRAWLER-UA\nX-Request-Id: req-8c85ba19abef566b\nContent-Type: application/json\n```\n\n### 2. Build the JSON request body\n\nSend publisherDomain (the request Host) and resourceUrl (the path; query strings are stripped before rule matching). Add crawlerAgent: the identified bot id, or the original user agent if unidentified. Add crawlerAutoPrice set to true so a paid resource is charged in one call when the balance allows. crawlerAutoPrice, crawlerExactPrice and crawlerMaxPrice are mutually exclusive; sending none means price discovery. Save these fields as verify-body.json.\n\n```bash\n# verify-body.json holds: publisherDomain, resourceUrl,\n# crawlerAutoPrice (true) and crawlerAgent\ncurl -X POST https://cdn.aisa.one/api/v1/access/verify \\\n  -H 'Authorization: Bearer YOUR_GATEWAY_TOKEN' \\\n  -H 'X-Crawler-Token: YOUR_CRAWLER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  --data @verify-body.json\n```\n\n### 3. Read accessGranted and decide\n\nA minimal gateway reads one field. When accessGranted is true, fetch the content from origin or cache and serve it with status 200. When it is false, do not serve content. Rules are first-match-wins: block gives 403 access_blocked, allow gives 200 free, pay needs a valid crawler token, and no matching rule gives 200 default allow with no charge. Charged responses carry charged and chargeAmount.\n\n### 4. Pass 402 and 403 bodies through unchanged\n\nOn accessGranted false with statusCode 402 or 403, return statusCode and the verify JSON body to the crawler exactly as received. The body carries onboarding and a guidance block that teach a new crawler to register, fund a balance and retry with X-Crawler-Token. A 402 means payment can unlock the resource; a 403 access_blocked means payment will not help. Render your own block page if you want one, since AISA returns JSON only.\n\n### 5. Handle gateway-side failures separately\n\nA 401 gateway_unauthorized means the gateway token is missing or invalid, so check the key configured in the console; this is an operator error, not a crawler one. A 400 means a parameter problem such as invalid_price_format or conflicting price fields. A 500 internal_error is a provider fault, handled by your timeout and fault policy. On timeout retries, reuse the same X-Request-Id so both sides can reconcile charges.\n\n## Verify outcomes and gateway action\n\n| Situation | Status and error | accessGranted | Gateway returns to crawler |\n| --- | --- | --- | --- |\n| allow rule, or no rule matched (default allow) | 200, no charge | true | Content from origin, 200 |\n| pay rule, valid token, enough balance, crawlerAutoPrice true | 200 with charged and chargeAmount | true | Content from origin, 200 |\n| pay rule, token missing or invalid | 402 invalid_token | false | 402 JSON passed through, with onboarding and guidance |\n| pay rule, balance too low | 402 insufficient_balance, crawler.shortfall | false | 402 JSON passed through, top up then retry |\n| pay rule, no price intent (price discovery) | 402 price_required, price and currency | false | 402 JSON passed through with the price quote |\n| exact or max price intent does not fit | 402 price_mismatch or price_too_high | false | 402 JSON passed through, retry with the real price or a higher budget |\n| block rule matched | 403 access_blocked | false | 403 JSON passed through, payment cannot unlock |\n| gateway or request errors | 401 gateway_unauthorized, 400 invalid_price_format, 500 internal_error | false | 401: check the gateway token in the console; 400: fix the request; 500: apply your fault policy |\n\n## Common pitfalls\n\n- Never send the crawler token in Authorization: Bearer; that header is reserved for the gateway layer and yields 401 gateway_unauthorized.\n- Do not send crawlerAutoPrice, crawlerExactPrice and crawlerMaxPrice together; they are mutually exclusive and a conflict returns a 400.\n- Do not rewrite or trim the 402 JSON. Its onboarding and guidance fields are how new crawlers learn to register and pay.\n- The original-agent header is spelled X-ESA-Original-Crawler-Agent in AISA docs and X-Original-Crawler-Agent in the Open Verify spec; the body field crawlerAgent carries the same data.\n\n## Key takeaways\n\n- Read accessGranted: true means fetch from origin, false means return statusCode and the JSON body to the crawler.\n- 402 means payment can unlock the resource; 403 access_blocked means it cannot; 401 is reserved for the gateway layer.\n- Send the crawler token in X-Crawler-Token and the gateway token in Authorization: Bearer, never mixed.\n\n## FAQ\n\n**What does the gateway send to POST /api/v1/access/verify?**\nAuthorization: Bearer with the gateway token, X-Crawler-Token forwarded from the crawler, the original crawler user agent header, and a JSON body with publisherDomain, resourceUrl, crawlerAutoPrice and crawlerAgent. X-Request-Id is optional.\n\n**What happens when the crawler has no token?**\nOn a pay rule, verify returns 402 with error invalid_token, plus onboarding and guidance. The gateway passes that JSON to the crawler so it can register and retry. On an allow rule, no token is needed and the response is 200.\n\n**What is the difference between 402 and 403?**\n402 means the resource is for sale and payment or registration can unlock it. 403 with access_blocked means the publisher blocked it, regardless of token or balance, so the crawler should not register or top up for that URL.\n\n**How do I get price discovery without being charged?**\nSend none of crawlerAutoPrice, crawlerExactPrice or crawlerMaxPrice. For a pay resource, verify returns 402 with error price_required plus price and currency, and does not charge.\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)"
}