Migrating from cors-anywhere to corsproxy.dev
If you're hitting cors-anywhere.herokuapp.com and seeing requests blocked at 50 per hour, this is the drop-in alternative. Same request shape, no demo limits, and the option to self-host the same Go binary we run.
Why cors-anywhere stopped working for you
The public cors-anywhere.herokuapp.com instance was always meant as a demo of the open-source Rob--W/cors-anywhere project, not a production service. In 2021 the maintainer added a temporary-access gate after sustained abuse; today the demo instance is rate-limited to ~50 requests per hour per IP and rejects unrecognised origins with HTTP 403.
That's by design — the maintainer is right that an open proxy on the public internet shouldn't be free at scale — but it leaves a lot of frontends quietly broken.
The migration in one diff
The request shape is identical. You change the host and add an API key header:
// Before — cors-anywhere demo
fetch('https://cors-anywhere.herokuapp.com/' +
'https://api.example.com/users')
// After — corsproxy.dev
fetch('https://api.corsproxy.dev/proxy?url=' +
encodeURIComponent('https://api.example.com/users'), {
headers: { 'X-API-Key': 'sk_live_...' }
})
Two differences worth flagging:
- The target URL is a query parameter, not a path suffix. URL-encode it (
encodeURIComponent). cors-anywhere's path-concatenation style works most of the time but breaks on URLs with query strings; the explicit?url=form is unambiguous. - You send an API key. Free tier is 100 requests/day, no credit card. Sign in with your email at app.corsproxy.dev, generate a key from the dashboard, copy it into your client.
Common patterns and their replacements
1. Wrapper function in your codebase
If you abstracted cors-anywhere into a helper, the replacement is one line:
// Before
const corsFetch = (url, opts) =>
fetch('https://cors-anywhere.herokuapp.com/' + url, opts);
// After
const PROXY = 'https://api.corsproxy.dev/proxy';
const KEY = import.meta.env.VITE_PROXY_KEY;
const corsFetch = (url, opts = {}) =>
fetch(`${PROXY}?url=${encodeURIComponent(url)}`, {
...opts,
headers: { ...opts.headers, 'X-API-Key': KEY }
});
2. Axios baseURL
// Before
const api = axios.create({
baseURL: 'https://cors-anywhere.herokuapp.com/https://api.example.com'
});
// After
const api = axios.create({
baseURL: 'https://api.corsproxy.dev/proxy',
headers: { 'X-API-Key': process.env.REACT_APP_PROXY_KEY }
});
// Then call like:
api.get('?url=' + encodeURIComponent('https://api.example.com/users'));
// Or wrap it more cleanly:
const proxied = (path) =>
api.get('?url=' + encodeURIComponent('https://api.example.com' + path));
3. Background fetcher / server-side script
If the requests are coming from your backend (a cron job, a Lambda, anything not in a browser), you probably don't need a CORS proxy at all — CORS is a browser-only restriction. Drop the proxy entirely and call the upstream directly.
What you get that cors-anywhere doesn't have
- Stable hostname. No more demo-instance rotations or sudden 403s.
- Real rate limits. Per-key daily quota enforced atomically, so usage from one engineer's local dev doesn't eat the whole team's allowance.
- Usage dashboard. Per-key, per-day request counts and 7-day trend chart. You can see what's actually hitting the proxy.
- Audit logs. 30-day retention of the URLs each key requested, available in the dashboard.
- Same open-source path. If you outgrow the managed tier or want full control, self-host the Go binary. Migration is just a host change.
What you give up vs running your own cors-anywhere
The original cors-anywhere supports response caching and a few header-forwarding modes that we deliberately don't ship for security reasons — see our security writeup. If you rely on those, the closest path is to fork our OSS binary and add the behavior you need.
One-line migration
Get a free key, swap the host, and you're back to working CORS proxying in five minutes.