Tutorial

Call OpenAI, Notion, and Google Sheets from the browser without leaking your API key

These three APIs all send CORS headers, so a plain fetch() from the browser goes through. The problem is the key: anything in client-side JavaScript is public. Here's how to keep it out of the browser without writing a backend, and what that setup still can't protect.

· ~6 min read

CORS isn't the blocker here, the key is

OpenAI, Notion, and Google's APIs all return Access-Control-Allow-Origin. Check it yourself:

curl -sI -H "Origin: https://example.com" https://api.notion.com/v1/users | grep -i access-control-allow-origin
access-control-allow-origin: *

So the request works. The catch is that anything you ship to the browser, including bundled code, environment variables baked in at build time, and headers visible in devtools, can be read by any visitor. An OpenAI key in a React app is someone else's free GPU budget, and a Notion integration token gives access to every page the integration was shared with.

The usual fix is a small backend that holds the secret and forwards the call. corsproxy.dev's managed upstream headers do the same thing without you running the backend: you store the secret on your corsproxy.dev API key once, and the proxy adds it to requests going to the host you name.

OpenAI

Create a key that injects your OpenAI secret, but only for one path:

POST /v1/api-keys
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "name": "Chat widget",
  "allowed_origins": ["https://app.example.com"],
  "upstream_rules": [{
    "target_host": "api.openai.com",
    "path_prefix": "/v1/chat/completions",
    "headers": { "Authorization": "Bearer sk-YOUR_OPENAI_KEY" }
  }]
}
// Browser: no OpenAI key anywhere in this code
const target = encodeURIComponent('https://api.openai.com/v1/chat/completions');
const res = await fetch(`https://api.corsproxy.dev/proxy?url=${target}&key=YOUR_CORSPROXY_KEY`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    model: 'gpt-5-mini',
    messages: [{ role: 'user', content: 'Hello' }]
  })
});
const data = await res.json();

A narrow path_prefix matters. If it were just /v1/, anyone holding your corsproxy.dev key could reach every OpenAI endpoint with your secret, including file uploads and fine-tuning.

Notion

Notion needs two headers: Authorization and Notion-Version. Only the first one is secret. The proxy passes your browser's own request headers through (except Authorization, Origin, Referer, and Host), so send the version from the client and inject only the token. That also fits the free plan's limit of one injected header per key.

"upstream_rules": [{
  "target_host": "api.notion.com",
  "path_prefix": "/v1/databases/",
  "headers": { "Authorization": "Bearer ntn_YOUR_NOTION_TOKEN" }
}]
const target = encodeURIComponent('https://api.notion.com/v1/databases/DATABASE_ID/query');
const res = await fetch(`https://api.corsproxy.dev/proxy?url=${target}&key=YOUR_CORSPROXY_KEY`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Notion-Version': '2022-06-28' },
  body: JSON.stringify({ page_size: 10 })
});
const { results } = await res.json();

Google Sheets

Google has its own answer here: restrict the API key to your site's HTTP referrers in Google Cloud Console, and a key that's visible in the browser matters much less. Do that first. If you'd rather the key not appear in the browser at all, Google APIs also accept it in the X-Goog-Api-Key header instead of ?key=, so you can inject it:

"upstream_rules": [{
  "target_host": "sheets.googleapis.com",
  "path_prefix": "/v4/spreadsheets/SHEET_ID/",
  "headers": { "X-Goog-Api-Key": "YOUR_GOOGLE_API_KEY" }
}]
const target = encodeURIComponent(
  'https://sheets.googleapis.com/v4/spreadsheets/SHEET_ID/values/A1:C10'
);
const res = await fetch(`https://api.corsproxy.dev/proxy?url=${target}&key=YOUR_CORSPROXY_KEY`);
const { values } = await res.json();

What this doesn't protect

When you don't need any of this

If the API is yours, add CORS headers on your own server (see our Express CORS guide) and keep secrets there. If the API needs no key and already sends CORS headers, call it directly; a proxy only adds latency. Full field reference: API docs → Managed upstream headers.

Try it

Get a free API key — 500 requests/day, no credit card. Store your first upstream secret in a couple of minutes.