BYOKchat Blog

Why Some AI APIs Don't Work in the Browser: CORS Explained

Learn why an AI API can work in curl but fail in a web app, how CORS and preflight requests work, and the safe options for BYOK and local AI clients.

· 8 min read

On this page
  1. What is an origin?
  2. Why browsers have this restriction
  3. A simple CORS request
  4. Why API keys often trigger a preflight request
  5. Why it works in curl but not in the browser
  6. CORS is enforced by the browser, but configured by the server
  7. CORS is not authentication
  8. Should an AI provider allow browser CORS?
  9. Never solve CORS by publishing your own shared API key
  10. Direct browser API vs server proxy
  11. Why native apps do not hit the same browser CORS wall
  12. CORS and local AI servers
  13. CORS and remote MCP servers
  14. Common CORS error patterns
  15. No Access-Control-Allow-Origin
  16. Preflight request failed
  17. Header not allowed
  18. Method not allowed
  19. Credentials/wildcard mismatch
  20. A systematic debugging sequence
  21. Check the browser Network panel
  22. Compare with curl
  23. Test the exact origin
  24. Should you set Access-Control-Allow-Origin: *?
  25. Avoid random public “CORS proxy” websites
  26. What this means for BYOKchat Web
  27. Bottom line
  28. Further reading

If an AI API works in curl, Postman, or a native app but fails when JavaScript calls it from a browser, the problem may be CORS rather than your API key or model configuration.

CORS—Cross-Origin Resource Sharing—is a browser security mechanism. It lets a server explicitly tell the browser which web origins are allowed to read cross-origin responses.

The key fact is:

curl / native app
    → sends HTTP request directly
    → no browser CORS enforcement

browser JavaScript
    → browser enforces same-origin policy + CORS
    → server must permit the web origin

This is why copying a working API base URL into a web BYOK client does not guarantee that the browser can use it.

What is an origin?

For browser security, an origin is based on the URL scheme, host, and port.

These are different origins:

https://chat.byok.pro
https://api.example.com
http://localhost:11434
http://192.168.1.50:1234

When JavaScript running on https://chat.byok.pro requests https://api.example.com, that is a cross-origin request.

The browser decides whether the calling page is allowed to read the response according to the server’s CORS headers and the request type.

Why browsers have this restriction

Without same-origin protections, any malicious website you visit could try to make authenticated requests to other sites or local services from your browser and read the results.

Imagine this page:

https://evil.example

silently trying to read:

https://bank.example/account
http://localhost:1234/v1/models
http://192.168.1.20/admin

Browsers need boundaries around what one origin can read from another.

CORS is a controlled way for the target server to say:

"I intentionally allow JavaScript from this origin to read my response."

It is not merely an annoying setting that browsers invented to break APIs.

A simple CORS request

Suppose BYOKchat Web is loaded from:

Origin: https://chat.byok.pro

and tries to call:

https://api.example.com/v1/models

A simplified successful response might include a header such as:

Access-Control-Allow-Origin: https://chat.byok.pro

The browser can then allow the page to read the response, assuming the rest of the CORS rules are satisfied.

If the server does not return the required permission, the browser can block JavaScript from accessing the response even though the network request may have reached the server.

Why API keys often trigger a preflight request

AI APIs commonly use an Authorization header and JSON request bodies. Those requests frequently require the browser to perform a CORS preflight before sending the real request.

The browser first sends an OPTIONS request that asks whether the actual request is allowed.

Conceptually:

Browser

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

API server

   │ Access-Control-Allow-Origin: https://chat.byok.pro
   │ Access-Control-Allow-Methods: POST, OPTIONS
   │ Access-Control-Allow-Headers: Authorization, Content-Type

Browser

   │ POST /v1/chat/completions
   │ Authorization: Bearer …

API server

If the OPTIONS request fails, omits the necessary headers, redirects unexpectedly, or is not handled at all, the browser may never send the actual model request.

Why it works in curl but not in the browser

curl does not implement the browser same-origin security model.

So this can succeed:

curl https://api.example.com/v1/models \
  -H "Authorization: Bearer …"

while browser JavaScript fails with a CORS error.

That tells you something useful:

Server reachable?        probably yes
Credential valid?        maybe
Browser permitted?       not necessarily

Do not rotate a perfectly good API key merely because the browser console says “blocked by CORS.”

CORS is enforced by the browser, but configured by the server

A common misconception is that the web app can “enable CORS” for a remote API.

It cannot.

The server being called must return the relevant CORS response headers. JavaScript cannot grant itself permission to bypass the browser’s security policy.

This means fixes such as these do not solve the real problem:

fetch(..., { mode: "no-cors" })

no-cors produces an opaque response that JavaScript generally cannot inspect as a normal API response. It is not a way to turn an incompatible JSON API into a usable one.

Likewise, adding an Access-Control-Allow-Origin header to the request does not help. That is a response policy controlled by the server.

CORS is not authentication

CORS and API authentication solve different problems.

Authentication
    "Is this caller allowed to use the API?"

CORS
    "May JavaScript from this browser origin read the response?"

A server can require a valid API key and allow a specific web origin.

It can also allow CORS broadly while still rejecting requests without valid authentication.

Do not use permissive CORS as a substitute for authentication on a sensitive local or remote service.

Should an AI provider allow browser CORS?

It depends on the provider’s intended architecture and threat model.

Some APIs are intentionally usable directly from browser applications. Others assume credentials are stored on a backend server and therefore do not permit arbitrary browser origins.

A provider may be cautious about browser CORS because exposing a secret developer-owned API key in shipped frontend code is dangerous.

This is where BYOK changes the context: the user supplies their own credential locally rather than the website shipping one shared developer secret to every visitor.

But provider policy still wins. If the API does not support direct browser clients, a BYOK web app cannot safely pretend otherwise.

Never solve CORS by publishing your own shared API key

This architecture is unsafe:

JavaScript bundle
└── hard-coded developer API key

        └── visible to every user who downloads the app

CORS permission does not hide a secret embedded in frontend code.

For a public application using one developer-owned credential, a server-side backend/proxy is usually required so the secret stays on infrastructure you control.

In a true BYOK web client, the user enters their own provider credential. Even then, the browser and provider must support the direct connection.

Read How to Store API Keys Safely.

Direct browser API vs server proxy

When a provider does not support browser CORS, one common architecture is a backend proxy:

Browser


Your server


AI provider

The server is not subject to browser CORS when it makes the provider request.

But a proxy changes the privacy and security boundary. If prompts or credentials pass through that server, your infrastructure becomes part of the trust chain.

Compare:

Direct BYOK browser path
browser → provider

Proxy path
browser → application server → provider

The proxy can add useful functionality—authentication, billing, policy enforcement, caching, auditing—but it is not free from privacy consequences.

See Direct-to-Provider AI vs Proxy Servers.

Why native apps do not hit the same browser CORS wall

CORS is part of the browser web-security model. A native iOS or macOS networking stack is not a web page making a cross-origin fetch, so browser CORS headers do not gate normal native HTTP requests in the same way.

That is one practical difference between a web BYOK client and a native one:

Web app
    provider must support browser-origin access

Native app
    no browser CORS requirement
    but normal TLS/network/platform security rules still apply

This does not mean native apps may ignore provider authentication, TLS, or API policy. It simply removes the browser CORS layer.

Read BYOK Web App vs Native App: What Changes?.

CORS and local AI servers

Local AI is where CORS surprises many users.

You may run Ollama, LM Studio, llama.cpp, or another server on your Mac and confirm that this works:

curl http://localhost:1234/v1/models

Then you open a web client on another origin and it fails.

Possible reasons include:

  • the server does not permit that browser origin;
  • the preflight OPTIONS request is not handled;
  • Authorization is not listed in allowed headers;
  • the server is bound only to loopback;
  • the browser is on another device and localhost points to that device itself;
  • HTTPS mixed-content or private-network restrictions apply;
  • a firewall blocks the LAN request.

CORS is only one layer of local-network troubleshooting.

Read How to Debug Local AI Connection Failures and Why localhost Does Not Work From Your Phone.

CORS and remote MCP servers

Browser-based MCP clients face the same basic constraint.

If a remote MCP endpoint is called directly from browser JavaScript, the server must permit the web origin and required HTTP methods/headers.

A tool server can be perfectly valid according to the MCP protocol and still be unusable from a browser because its HTTP deployment lacks browser CORS support.

This distinction is useful:

MCP protocol compatible       ✓
Browser HTTP/CORS compatible  ?

Both must be true for a direct web MCP connection.

Common CORS error patterns

Browser messages vary, but these patterns are common.

No Access-Control-Allow-Origin

The server did not authorize your web origin to read the response.

Preflight request failed

The browser’s OPTIONS request was rejected, redirected, timed out, or returned incomplete CORS permission headers.

Header not allowed

The actual request wants to send a header such as Authorization, but the preflight response did not allow it.

Method not allowed

The browser wants to send POST, DELETE, or another method that the CORS policy did not permit.

Credentials/wildcard mismatch

Requests involving browser-managed credentials have stricter rules around wildcard origins. Do not blindly combine * with credentialed flows.

A systematic debugging sequence

When a web AI connection fails, debug in layers:

1. DNS / address correct?
2. Server reachable at all?
3. TLS / HTTPS valid?
4. API endpoint path correct?
5. CORS preflight succeeds?
6. Required headers allowed?
7. Authentication valid?
8. Model ID valid?
9. Request schema supported?
10. Streaming parser compatible?

This order prevents you from treating every failure as an API-key problem.

Check the browser Network panel

Look for the OPTIONS request and the actual request separately.

If only OPTIONS appears, inspect its status and response headers.

If the POST is sent and returns a provider JSON error, CORS may already be working and the problem is elsewhere.

Compare with curl

A successful curl request proves useful pieces of the path, but not browser compatibility.

Test the exact origin

A server that allows:

http://localhost:3000

may still reject:

https://chat.byok.pro

Origins are exact security identities, not vague domain families.

Should you set Access-Control-Allow-Origin: *?

Only if that policy genuinely fits the service.

For a public unauthenticated API, a wildcard may be appropriate. For a sensitive local management endpoint, it may be far too broad.

Prefer designing CORS from the service’s threat model:

Who should call this endpoint?
From which web origins?
With which methods?
With which headers?
With what authentication?

Do not copy a permissive CORS snippet solely because it makes the browser error disappear.

Avoid random public “CORS proxy” websites

A public CORS proxy can make an incompatible API appear reachable by forwarding the request through someone else’s server.

For an AI API request, that can expose:

  • your API key;
  • prompts and chat context;
  • attachments;
  • model responses;
  • tool data.

The data path becomes:

browser → unknown proxy → AI provider

That is usually a terrible tradeoff for a credentialed BYOK workflow.

If you truly need a proxy, use infrastructure you control and understand the new trust boundary.

What this means for BYOKchat Web

BYOKchat Web is designed for direct provider and remote-MCP communication where the endpoint supports browser access.

That architecture has a strong privacy advantage: BYOK infrastructure does not need to proxy every AI prompt merely to make the product function.

The tradeoff is that a provider or custom endpoint that refuses browser CORS may work in native BYOKchat but not in the web version.

That is a real platform limitation, not something the UI should hide.

Bottom line

If an AI API works in curl but fails in a browser, check CORS before blaming the API key.

CORS is the server-controlled permission layer that tells a browser whether JavaScript from one origin may read a response from another origin. Native clients and command-line tools do not enforce browser CORS, which is why the same endpoint can behave differently across platforms.

The safe fixes are to use a browser-compatible provider, configure CORS correctly on an endpoint you control, use a trusted native client, or deliberately introduce a backend proxy whose privacy/security consequences you understand.

Do not bypass the problem with leaked frontend secrets or an unknown public CORS proxy.

Further reading

Keep reading