Tutorial

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

APIs like OpenAI, Notion, and Google Sheets need a secret, and anything in client-side JavaScript is public. This post shows how to keep that secret out of the browser without writing a backend, and what the setup still can't protect.

· ~6 min read · Updated Oct 13, 2026

The secret key is the real constraint

Some APIs send CORS headers and some don't. You can check any endpoint by sending a request with an Origin header and looking for Access-Control-Allow-Origin in the response. But even when an API does allow browser calls, it doesn't solve the bigger problem: 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, and can only call OpenAI:

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

{
  "name": "Chat widget",
  "allowed_origins": ["https://app.example.com"],
  "allowed_targets": ["api.openai.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 Host, Origin, Referer, Authorization, Cookie, and X-API-Key, 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

Create a free key and store your first upstream secret from the dashboard.