Skip to content

Instantly share code, notes, and snippets.

@monperrus
Created June 9, 2026 18:43
Show Gist options
  • Select an option

  • Save monperrus/5e2b653e404b6b97ad4ec913b731ae90 to your computer and use it in GitHub Desktop.

Select an option

Save monperrus/5e2b653e404b6b97ad4ec913b731ae90 to your computer and use it in GitHub Desktop.
CORS Preflight (HTTP OPTIONS) Support for Agent Endpoints

CORS Preflight (HTTP OPTIONS) Support for Agent Endpoints

This document describes CORS (Cross-Origin Resource Sharing) preflight support for all LLM API endpoints used by the agent specs in this project.

What is CORS?

CORS is a browser security mechanism that restricts cross-origin HTTP requests. Before making an actual request, browsers send a preflight OPTIONS request to check if the server allows the cross-origin request. The server must respond with appropriate Access-Control-Allow-* headers for the browser to proceed.

Why CORS Matters for LLM Endpoints

LLM API endpoints need to be CORS-open for several important use cases:

  • Browser-based coding agents — Web UIs and browser extensions that call LLM APIs directly from the user's browser (e.g., OpenCode Web, custom dashboards) are blocked by the browser's same-origin policy if the endpoint doesn't support CORS.
  • Client-side SDKs — JavaScript/TypeScript SDKs running in the browser (e.g., Vercel AI SDK, LangChain.js) make fetch requests to LLM endpoints. Without CORS, these SDKs cannot be used from web applications.
  • Cross-origin tool integrations — Tools like Obsidian plugins, VS Code web extensions, or Jupyter notebooks running in a browser environment need CORS to call LLM APIs from their own origin.
  • Streaming from web apps — Server-Sent Events (SSE) for streaming LLM responses require CORS preflight to work in browser contexts.
  • Development and prototyping — Local web apps during development (e.g., localhost:3000) need CORS to test against production API endpoints.

Without CORS support, these use cases are forced to use server-side proxies, which adds latency, complexity, and cost.

Test Method

Each endpoint was tested with an HTTP OPTIONS request to /chat/completions with standard preflight headers:

OPTIONS /v1/chat/completions
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type

A PASS verdict means the server returned Access-Control-Allow-Origin and related headers.

Results

# Endpoint Status Verdict CORS Headers
1 OpenRouter https://openrouter.ai/api/v1 204 ✅ PASS Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,OPTIONS,PATCH,DELETE,POST,PUT
Access-Control-Allow-Headers: Authorization, Content-Type, ...
2 Azure https://assert-service.services.ai.azure.com/... 200 ✅ PASS Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization, content-type
4 ModelStream https://api.modelstream.ai/v1 204 ✅ PASS Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS,PATCH,HEAD
Access-Control-Allow-Headers: Origin, Content-Type, Authorization, ...
Access-Control-Max-Age: 43200
5 NVIDIA NIM https://integrate.api.nvidia.com/v1 200 ❌ FAIL No Access-Control-Allow-* headers returned (only Vary: Origin)
7 OpenCode Zen https://opencode.ai/zen/v1 404 ❌ FAIL No CORS headers — OPTIONS falls through to frontend SPA router

Per-Endpoint Details

✅ OpenRouter

  • Status: 204 No Content
  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET,OPTIONS,PATCH,DELETE,POST,PUT
  • Access-Control-Allow-Headers: Authorization, User-Agent, X-Api-Key, ... (extensive list)
  • Models: 22 models including Claude, Gemini, Llama, Mistral, Qwen, Kimi, etc.

✅ Azure AI Foundry

  • Status: 200 OK
  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: POST
  • Access-Control-Allow-Headers: authorization, content-type

✅ ModelStream

  • Status: 204 No Content
  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS,PATCH,HEAD
  • Access-Control-Allow-Headers: Origin, Content-Type, Content-Length, Accept, Accept-Language, Accept-Encoding, Authorization, Cache-Control, Modelstream-User, X-Requested-With, X-Conversation-Id, X-Auto-Conversation, X-Signature, Stripe-Signature, X-Api-Key, X-Goog-Api-Key, Anthropic-Version, Anthropic-Beta, Mj-Api-Secret, Sec-Websocket-Protocol
  • Access-Control-Max-Age: 43200 (12 hours)
  • History: Previously returned 403 with no CORS headers. Fixed by ModelStream on 2026-06-09.

❌ NVIDIA NIM

  • Status: 200 OK
  • Issue: Returns Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers but no actual Access-Control-Allow-* headers. The Vary header suggests the server intends to handle CORS, but the response is missing the required headers.
  • Models: meta/llama-4-maverick, mistralai/mistral-nemotron, nvidia/nemotron-mini-4b-instruct, stepfun-ai/step-3.5-flash

❌ OpenCode Zen

  • Status: 404 Not Found
  • Issue: OPTIONS requests fall through to the frontend SPA router, which returns an HTML "Not Found" page. The actual POST endpoints work (return 401 "Invalid API key"), but the API gateway does not handle OPTIONS preflight.
  • Models: nemotron-3-super-free
  • Tracking: GitHub issue #31041 on anomalyco/opencode
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment