Geodocs.dev

CORS Headers for AI Search Snippets

ShareLinkedIn

Open this article in your favorite AI assistant for deeper analysis, summaries, or follow-up questions.

AI search engines fetch most pages server-side and ignore CORS, but the browser-side widgets that render citations, embed previews, and load content into AI assistant UIs do enforce CORS. Misconfigured Access-Control-Allow-Origin, preflight, or credential headers can silently break citation widgets while leaving server-side crawls untouched.

TL;DR

CORS is a browser-enforced policy. Server-side AI crawlers like GPTBot, PerplexityBot, and ClaudeBot do not need it. But AI assistant front-ends, in-browser snippet renderers, and client-side citation widgets do. Set Access-Control-Allow-Origin to a strict allowlist (or for fully public assets), respond correctly to preflight OPTIONS, and never combine Access-Control-Allow-Credentials: true with . Misconfiguration is a silent citation blocker.

When AI Search Engines Need CORS

Cross-Origin Resource Sharing (CORS) is an HTTP-header mechanism that lets a server tell browsers which other origins may read its responses (MDN: CORS). It is enforced by user agents, not by servers, and it applies to XMLHttpRequest and fetch() calls, not to direct server-to-server crawling.

AI ecosystems split into two retrieval modes, only one of which is affected:

  • Server-side crawl and indexing. Bots like GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, ChatGPT-User, Perplexity-User, and Google-Extended fetch your origin directly from their own infrastructure. They do not run inside a browser and do not enforce CORS. Robots.txt and server access controls govern this path, not CORS.
  • Browser-side rendering and embeds. AI assistant UIs (ChatGPT web, Claude web, Perplexity, AI Overviews iframe widgets), in-product preview cards, and third-party citation widgets that re-fetch your URL from the user's browser do enforce CORS. So do JavaScript embeds, JSON feeds consumed by AI clients, and Web Share Target style flows.

If you serve static HTML and your AI traffic is server-side citations only, you may never need CORS. The moment any AI surface tries to re-fetch your content from the user's browser — to render a snippet, hydrate a card, or run a client-side preview — you are inside the browser CORS contract.

Required Response Headers

The minimum useful CORS response is one header:

Access-Control-Allow-Origin: https://chat.openai.com

For public assets that any origin may consume (open APIs, public JSON feeds, OG-style previews), the wildcard form is acceptable:

Access-Control-Allow-Origin: *

Four additional response headers come into play depending on the request type (MDN: Access-Control-Allow-Origin):

  • Access-Control-Allow-Methods — allowed verbs on preflight (e.g., GET, HEAD, OPTIONS).
  • Access-Control-Allow-Headers — list of request headers the client may send (e.g., Content-Type, Authorization). Wildcard * works only for unauthenticated requests; Authorization must always be listed explicitly.
  • Access-Control-Allow-Credentials: true — only set if cookies or HTTP auth must travel; cannot be combined with Access-Control-Allow-Origin: *.
  • Access-Control-Expose-Headers — response headers the script may read (e.g., ETag, Last-Modified).
  • Access-Control-Max-Age — seconds the browser may cache the preflight (commonly 600-86400).

For AI snippet endpoints, the safe public default is:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With
Access-Control-Max-Age: 86400
Vary: Origin

Always add Vary: Origin whenever the value of Access-Control-Allow-Origin is computed from the request's Origin header, so caches do not serve the wrong allowlist to the wrong origin.

Preflight (OPTIONS) Mechanics

Non-simple cross-origin requests — anything with a custom header, a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, or a method other than GET, HEAD, POST — trigger a CORS preflight: an OPTIONS request the browser sends before the real request.

A correct preflight response looks like:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.perplexity.ai
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
Vary: Origin

Preflight pitfalls that silently break AI snippet hydration:

  • Returning 404 or 405 to OPTIONS. Many app servers reject OPTIONS by default.
  • Returning 200 with no CORS headers — the browser still treats it as a CORS failure.
  • Forgetting to echo every header the client actually sends in Access-Control-Allow-Headers (e.g., X-Client-Version, Authorization).
  • Caching the preflight at a CDN without Vary: Origin, so a second client gets the first client's allowlist.

Credentialed Requests and Auth

If an AI client sends cookies or Authorization headers (rare for public snippet hydration, common for authenticated SaaS embeds), credentialed CORS rules apply (MDN: Access-Control-Allow-Credentials):

  • Access-Control-Allow-Origin must be a single explicit origin, never *.
  • Access-Control-Allow-Credentials: true must be set on both preflight and main response.
  • Access-Control-Allow-Headers must list Authorization explicitly; the wildcard does not cover it.
  • Access-Control-Allow-Methods must include the actual method.

A wildcard origin paired with credentials is the most common production CORS bug; the request is rejected by the browser with an explicit console error.

Edge Cases for Embeds and Snippets

  • JSON-LD or structured data endpoints. If you serve a separate JSON feed (e.g., /feed.json, /llms.txt, an OpenAPI schema) that AI clients fetch from a browser, expose it with Access-Control-Allow-Origin: * and a long Access-Control-Max-Age. These are usually safe to fully open.
  • Image and asset hotlinking by snippet cards. Image responses do not enforce CORS unless the client explicitly opts into CORS (e.g., for reads). Snippet cards that draw your favicon or preview image into a canvas will silently fail without Access-Control-Allow-Origin on the asset response.
  • Range requests for media. Streaming previews use Range requests; ensure Access-Control-Expose-Headers: Content-Length, Content-Range, Accept-Ranges so the player can interpret partial content.
  • Subdomain APIs. If the page is on example.com and the API is on api.example.com, browsers treat them as different origins. Either return CORS headers from api.example.com or proxy through the same origin.
  • CDN edge caching. A CDN that caches a 200 response without honoring Vary: Origin will serve a CORS allowlist meant for one origin to another. Always include Vary: Origin on dynamic CORS responses.
  • Cross-Origin-Resource-Policy (CORP). Independently of CORS, Cross-Origin-Resource-Policy blocks no-cors cross-site loads. For public assets used by AI surfaces, set Cross-Origin-Resource-Policy: cross-origin (MDN: CORP).

Reference Configurations

Nginx (public read-only snippet endpoint)

location /snippets/ {
    add_header Access-Control-Allow-Origin "*" always;
    add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type" always;
    add_header Access-Control-Max-Age "86400" always;
    add_header Vary "Origin" always;
    if ($request_method = OPTIONS) { return 204; }
}

Cloudflare Workers (allowlist by Origin)

js

const ALLOWED = new Set([

"https://chat.openai.com",

"https://www.perplexity.ai",

"https://claude.ai",

]);

export default {

async fetch(req) {

const origin = req.headers.get("Origin") || "";

const allow = ALLOWED.has(origin) ? origin : "";

const headers = {

"Access-Control-Allow-Origin": allow,

"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",

"Access-Control-Allow-Headers": "Content-Type",

"Vary": "Origin",

};

if (req.method === "OPTIONS") return new Response(null, { status: 204, headers });

const upstream = await fetch(req);

const out = new Response(upstream.body, upstream);

Object.entries(headers).forEach(([k, v]) => out.headers.set(k, v));

return out;

},

};

Express (Node.js)

js

app.use("/snippets", (req, res, next) => {

res.setHeader("Access-Control-Allow-Origin", "*");

res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");

res.setHeader("Access-Control-Allow-Headers", "Content-Type");

res.setHeader("Access-Control-Max-Age", "86400");

res.setHeader("Vary", "Origin");

if (req.method === "OPTIONS") return res.status(204).end();

next();

});

Debugging Checklist

  1. Reproduce the failing fetch in a browser DevTools console; copy the exact error.
  2. Inspect the Network panel and confirm whether a preflight OPTIONS was sent. If it failed, fix the preflight before touching the main response.
  3. Confirm the response includes Access-Control-Allow-Origin exactly matching the request Origin (or * for non-credentialed).
  4. If credentials are used, verify Access-Control-Allow-Credentials: true is present and the origin is explicit, not *.
  5. Check Vary: Origin on dynamic responses; absence causes CDN poisoning.
  6. Validate with curl -I -H "Origin: https://chat.openai.com" https://example.com/snippets/foo and look for the headers in the raw response.
  7. If the body is correct but the browser still blocks, confirm the response status is 2xx — a 5xx with CORS headers still fails.

Common Mistakes

  • Setting Access-Control-Allow-Origin: * together with Access-Control-Allow-Credentials: true. The browser rejects the response.
  • Returning CORS headers only on 200 and not on OPTIONS preflight responses.
  • Echoing the Origin header back without an allowlist, effectively trusting any origin while pretending to be strict.
  • Forgetting Vary: Origin on dynamic responses, causing CDN cache poisoning.
  • Treating CORS as a substitute for authentication; CORS is a browser policy and does not protect against direct server-to-server fetches.
  • Assuming AI crawlers like GPTBot need CORS — they do not. CORS is irrelevant to robots.txt and server-side crawling.

FAQ

Q: Do GPTBot or PerplexityBot need CORS headers?

No. They are server-side crawlers and do not enforce CORS. CORS only matters for browser-side fetches such as AI assistant UIs hydrating a citation card from the user's browser.

Q: Is Access-Control-Allow-Origin: * safe?

For fully public, non-credentialed assets it is the simplest and safest choice. It is not safe whenever cookies, Authorization headers, or any session-bound data is involved.

Q: Why does my CORS work in curl but fail in the browser?

curl does not enforce CORS. The browser does. A working curl only proves the server returned a 2xx with the expected body; the browser additionally checks origin, methods, headers, and credentials.

Q: How do I support multiple origins without using *?

Maintain an allowlist on the server. On each request, compare the incoming Origin header to the allowlist and echo it back in Access-Control-Allow-Origin only if it matches. Always add Vary: Origin.

Q: Does CORS impact ranking or AI citation likelihood?

Indirectly. Failed browser-side hydration silently strips citation widgets from AI surfaces that re-fetch your URL client-side. Server-side citation eligibility is unaffected, but rendered previews may be missing.

Q: What status code should an OPTIONS preflight return?

Either 200 or 204. The body is ignored; only the CORS response headers matter. A 405 Method Not Allowed from a default app server is the most common preflight failure.

Related Articles

guide

404 Page AI Crawler Handling: Avoiding Citation Loss During Migrations

Migration playbook for keeping AI citations during URL changes — hard 404 vs soft 404, 410 Gone, redirect chains, sitemap cleanup, and refetch monitoring.

specification

Accept-Encoding (Brotli, Gzip) for AI Crawlers

Specification for serving Brotli, gzip, and zstd to AI crawlers via Accept-Encoding negotiation: which bots support which codecs, fallback rules, and Vary handling.

guide

Structured Data for AI Search

How to implement structured data (JSON-LD / Schema.org) to improve AI search visibility. Covers TechArticle, FAQPage, HowTo, and entity definitions.

Topics
Stay Updated

GEO & AI Search Insights

New articles, framework updates, and industry analysis. No spam, unsubscribe anytime.