Create a Cloudflare Worker that proxies API calls from a static frontend to a keyed API, keeping the API key server-side. Use when a client-only app (GitHub Pages, Netlify, Cloudflare Pages) needs to call an API that requires an API key — and exposing that key in the JavaScript bundle is unacceptable. Covers Worker code, CORS headers, secret storage, and the client-side fetch pattern. Verify current Cloudflare pricing, quotas, and product behavior from official documentation before presenting...
Scanned 9/3/2026
Install to Claude Code
npx -y skills add OKHP3/skillz --skill okhp3-cloudflare-worker-api-proxy --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Okhp3 Cloudflare Worker Api Proxy?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/okhp3-okhp3-cloudflare-worker-api-proxy)More formats (shields.io, HTML) on the badges page.
---
name: okhp3-cloudflare-worker-api-proxy
description: >
Create a Cloudflare Worker that proxies API calls from a static frontend to
a keyed API, keeping the API key server-side. Use when a client-only app
(GitHub Pages, Netlify, Cloudflare Pages) needs to call an API that requires
an API key — and exposing that key in the JavaScript bundle is unacceptable.
Covers Worker code, CORS headers, secret storage, and the client-side fetch
pattern. Verify current Cloudflare pricing, quotas, and product behavior from
official documentation before presenting them as guarantees.
license: MIT
metadata:
author: Jamie Hill (OverKill Hill P³)
version: "1.1.0"
category: universal
origin: kierans-lifetrkr
published-to: okhp3/skillz
homepage: https://overkillhill.com
author-github: https://github.com/OKHP3
in_scope: "Keeping API keys server-side while proxying authorized static-frontend requests through a Cloudflare Worker."
out_of_scope: "Client-side secret exposure, unrestricted proxying, credential rotation, or unrelated backend design."
compatibility: >
Cloudflare account and a static frontend on any host.
No Node.js or local tooling required — deploy via Cloudflare dashboard.
---
# okhp3-cloudflare-worker-api-proxy
**OverKill Hill P³** · [overkillhill.com](https://overkillhill.com) · [github.com/OKHP3](https://github.com/OKHP3)
A Cloudflare Worker is a serverless function that runs at Cloudflare's edge.
It handles the API call server-side, holds the API key in secrets, and returns
## Scope
| In scope | Out of scope |
|---|---|
| A narrow static-frontend-to-keyed-API proxy, CORS policy, secret binding, request validation, and client fetch example | Hiding a public endpoint, replacing authentication or authorization, rate-abuse prevention by assumption, or promising current plan limits without verification |
the result to your browser. The key never appears in your JavaScript bundle.
## When to use this
Use this pattern when:
- Your static site needs to call an API that requires a key (Anthropic, OpenAI, SendGrid, etc.)
- Exposing the key in client JavaScript would be a security risk or ToS violation
- You don't want to set up a full backend server
## The 30-line Worker
```javascript
// worker.js — paste this into the Cloudflare Worker editor
export default {
async fetch(request, env) {
// Handle CORS preflight
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': env.ALLOWED_ORIGIN || '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
},
});
}
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
// Parse the request body from the client
let body;
try {
body = await request.json();
} catch {
return new Response('Invalid JSON', { status: 400 });
}
// Call the upstream API with your secret key
const upstream = await fetch(env.UPSTREAM_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Add your API-specific auth header here:
'Authorization': `Bearer ${env.API_SECRET_KEY}`,
// Or for Anthropic:
// 'x-api-key': env.API_SECRET_KEY,
// 'anthropic-version': '2023-06-01',
},
body: JSON.stringify(body),
});
const data = await upstream.json();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': env.ALLOWED_ORIGIN || '*',
},
});
},
};
```
## Setup steps (dashboard, no CLI required)
1. **Create the Worker**
- Cloudflare dashboard → Workers & Pages → Create Worker
- Name it (e.g., `my-api-proxy`)
- Paste the Worker code above
- Edit `env.UPSTREAM_API_URL` reference and auth header to match your API
2. **Add secrets (not visible in code)**
- Worker → Settings → Variables → Add variable
- Add as **Secret** (encrypted at rest):
- `API_SECRET_KEY` = your actual API key
- `UPSTREAM_API_URL` = the API endpoint you're proxying (e.g., `https://api.anthropic.com/v1/messages`)
- `ALLOWED_ORIGIN` = your frontend's domain (e.g., `https://username.github.io`) — or `*` for open access
3. **Deploy**
- Click Deploy
- Worker URL: `https://my-api-proxy.your-account.workers.dev`
4. **Add the Worker URL to your frontend**
- Store as an environment variable: `VITE_PROXY_URL=https://my-api-proxy.your-account.workers.dev`
- In Replit: add to Secrets panel
## Client-side fetch pattern
```typescript
// In your React app — the key never appears here
const PROXY_URL = import.meta.env.VITE_PROXY_URL;
async function callApi(payload: object) {
const res = await fetch(PROXY_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(`Proxy error: ${res.status}`);
return res.json();
}
```
## Anthropic-specific variant
For proxying `api.anthropic.com/v1/messages`:
```javascript
// In the Worker, replace the fetch call with:
const upstream = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: body.model || 'YOUR_CURRENT_MODEL_ID',
max_tokens: body.max_tokens || 1000,
system: body.system,
messages: body.messages,
}),
});
```
## Current Cloudflare limits
Do not hard-code plan limits or a permanence claim in an implementation. Check the current official Workers pricing and limits documentation for the account, plan, runtime, and deployment date, then record the verified limits in the project deployment notes.
## CORS configuration
The `ALLOWED_ORIGIN` secret controls which domains can call your Worker:
```
ALLOWED_ORIGIN = https://username.github.io
```
Setting it to `*` allows any domain to call your Worker. Use your specific
frontend domain in production to prevent others from using your proxy.
## Security checklist
- [ ] API key stored as a Worker Secret (encrypted), not a plain variable
- [ ] `ALLOWED_ORIGIN` set to your specific frontend domain (not `*`)
- [ ] Worker only accepts POST (or the method your API uses)
- [ ] Worker validates the request body before forwarding
- [ ] Worker URL is in your frontend as an env var, not hardcoded
## What this does NOT protect against
- Someone hitting your Worker URL directly (anyone can POST to it if ALLOWED_ORIGIN is `*`)
- Rate abuse (add request counting logic if needed)
- The Worker URL being discovered from your frontend bundle (the URL is public — the key is not)
For higher-security scenarios: add an additional shared secret that the frontend sends
and the Worker validates before proxying.
## Output contract
Return the Worker source, secret names, allowed-origin policy, request/response shape, deployment steps, and a verification checklist. Never place a real key in source, logs, examples, or client code. Treat the proxy URL as public and state what abuse controls are still missing.
## About
Built by [Jamie Hill](https://overkillhill.com) · [OverKill Hill P³](https://overkillhill.com)
Published at [github.com/OKHP3](https://github.com/OKHP3)
Part of the [OKHP3/skillz](https://github.com/OKHP3/skillz) Agent Skill library.
MIT License -- free to use, fork, and adapt. A nod to the source is appreciated.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!