GuidesLOCAL APIS
Fix the Yelp API CORS error
Yelp's API does allow browser calls: on 28 October 2026 its preflight answered 204 with Access-Control-Allow-Origin: *, and Chromium read Yelp's JSON whenever the key had the right 128-character format. The CORS error appears when Yelp rejects a request before its key check, as with a missing or malformed Authorization header or a limit above 50, because those 400 responses carry no CORS headers. The free cors.dev proxy refuses Authorization; cors.dev Pro, $5 a month with a 7-day trial of 1,000 requests and no card, forwards it and adds CORS headers to every Yelp response, but your key stays visible either way.
What api.yelp.com sends a browser
The Yelp Places API, formerly Yelp Fusion, does send CORS headers, but only on part of its responses. We called https://api.yelp.com/v3/businesses/search on 28 October 2026 from Chromium on a US residential connection, from a cloud machine and through cors.dev. The preflight answered 204 with Access-Control-Allow-Origin: *, allowed the Authorization header and set Access-Control-Max-Age: 1728000.
With a fake key in Yelp's 128-character format, Chromium read Yelp's 401 JSON for /v3/businesses/search, /v3/businesses/{id} and POST /v3/graphql: {"error": {"code": "UNAUTHORIZED_ACCESS_TOKEN", ...}}.
| Request | Yelp status | CORS headers | What your code sees |
|---|---|---|---|
| Preflight OPTIONS for Authorization | 204 | Allow-Origin *, allows Authorization | Passes |
| GET with a fake 128-character key | 401 UNAUTHORIZED_ACCESS_TOKEN | Yes | Readable JSON, 138 bytes |
| POST /v3/graphql with that key | 401 | Yes | Readable JSON |
| GET without Authorization | 400 VALIDATION_ERROR | None | TypeError: Failed to fetch |
| GET with Bearer undefined or a quoted key | 400 VALIDATION_ERROR | None | TypeError: Failed to fetch |
| GET with limit=51 or radius=40001 | 400 VALIDATION_ERROR | None | TypeError: Failed to fetch |
| Fake key with credentials: include | 204 preflight | Wildcard refused by Chrome | TypeError: Failed to fetch |
| Any request from our cloud machine | 403, empty body | None | TypeError: Failed to fetch |
| Free cors.dev proxy, no key | 400 VALIDATION_ERROR | Added by cors.dev | Readable JSON, 140 bytes |
| Free cors.dev proxy with Authorization | 400 header_not_allowed | Added by cors.dev | Readable JSON from cors.dev |
Why fetch() to Yelp fails
Yelp checks the request format before it checks the key, and those 400 VALIDATION_ERROR responses carry no CORS headers. The browser hides the body and throws TypeError: Failed to fetch, and Chrome logs No 'Access-Control-Allow-Origin' header is present on the requested resource. The JSON you never see names the exact problem:
- Missing header: "Authorization is a required parameter."
- Wrong key format: "'Bearer abc123' does not match", followed by Yelp's pattern:
Bearerplus exactly 128 letters, digits, hyphens or underscores. SoBearer undefinedfrom an environment variable that never reached the client bundle, quotes around the key or a missingBearerprefix all fail here. A lowercasebearerpasses. - Out-of-range parameters: "51 is greater than the maximum of 50" for
limit, and "40001 is greater than the maximum of 40000" forradius. - Credentials:
credentials: 'include', orwithCredentials: truein axios, fails even with a valid header. Chrome says the Allow-Origin value "must not be the wildcard '*' when the request's credentials mode is 'include'". Yelp needs no cookies, so drop the option; the Access-Control-Allow-Credentials guide explains the rule. - Edge refusal: a
403with an empty body comes from Yelp's Varnish edge, not its API. Our cloud machine got it on every request, preflights included, even from Chromium. On the residential connection Chromium got through whilecurlstill got403, so try another network before you debug the key.
Chrome also logs Failed to load resource: the server responded with a status of 400, which tells you which case you hit.
Call Yelp from the browser
With a well-formed key and a network Yelp's edge accepts, plain fetch() works and you need no proxy. The snippet checks the key format first, so a missing variable fails with a clear message instead of a CORS error, and it reads the body of any error, including an empty 403.
const yelpKey = 'YOUR_YELP_API_KEY';
// Yelp drops its CORS headers when the key is malformed, so we check the format first
if (!/^[\w-]{128}$/.test(yelpKey)) {
throw new Error(`A Yelp key is 128 letters, digits, - or _, not ${yelpKey.length} characters`);
}
const params = new URLSearchParams({ location: 'Brooklyn, NY', term: 'pizza', limit: '5' });
const response = await fetch(`https://api.yelp.com/v3/businesses/search?${params}`, {
headers: { Authorization: `Bearer ${yelpKey}` },
});
if (!response.ok) {
const body = await response.text();
throw new Error(`Yelp answered ${response.status}: ${body.slice(0, 200) || 'empty body'}`);
}
const { businesses, total } = await response.json();
console.log(`${total} matches`);
for (const business of businesses) {
console.log(`${business.name}: ${business.rating} stars, ${business.review_count} reviews, ${business.url}`);
}Two gaps remain. Yelp's CORS headers include no Access-Control-Expose-Headers, so this code cannot read the RateLimit-Remaining header Yelp documents on each response. And the key sits in your page for anyone to copy.
Your Yelp key is a secret
Yelp's authentication docs say: "Please keep the API Key to yourself since it is the credential for your call to Yelp's API." A key in browser code is visible in DevTools to every visitor, who can then spend your calls. If one leaks, refresh it on Yelp's Manage App page.
Those calls cost money. Yelp ended free access in July 2024, when it moved existing free users to paid licences, and its pricing page, updated 12 October 2026, lists three plans:
- Base: $229 a month plus $5.91 per extra 1,000 calls, with no photos or review excerpts.
- Enhanced: $299 a month plus $6.57 per extra 1,000 calls, with up to 3 photos and 3 review excerpts.
- Premium: $643 a month plus $14.13 per extra 1,000 calls, with up to 12 photos, 7 review excerpts and Review Highlights.
Each plan starts with a 30-day trial of 5,000 calls that needs a business email and is "strictly for evaluation and not for commercial deployment". Yelp's FAQ says paid plans include 30,000 calls a month with up to 5,000 a day, and above 150,000 a month you talk to sales. For a public app, keep the key on your own backend; the proxy versus backend comparison covers the options.
Where cors.dev helps
The free proxy, https://proxy.cors.dev/ in front of the URL, cannot carry the key: it answers an Authorization header with 400 header_not_allowed and "Authorization is only accepted with a Pro key". Called without a key, it still returns Yelp's 400 body with CORS headers, so you can read the validation message the browser hides.
cors.dev Pro forwards Authorization from origins you register. Every Yelp response comes back with CORS headers, the 400s included, and cors.dev lists each upstream header in Access-Control-Expose-Headers, so RateLimit-Remaining becomes readable. From the cloud machine where Yelp's edge answered 403, the same request through cors.dev reached Yelp and returned its JSON error.
- Start the trial
Sign in with Google at cors.dev/signup for 7 days and 1,000 requests, no card.
- Register your origin
Add
http://localhost:5173or your app's origin and copy the Connection key. - Send both headers
Put the Connection key in
X-Cors-Keyand your Yelp key inAuthorization: Bearer, and callhttps://proxy.cors.dev/https://api.yelp.com/v3/....
const PROXY = 'https://proxy.cors.dev/';
const yelpKey = 'YOUR_YELP_API_KEY';
const params = new URLSearchParams({ location: 'Austin, TX', term: 'tacos', limit: '5' });
const response = await fetch(`${PROXY}https://api.yelp.com/v3/businesses/search?${params}`, {
headers: { 'X-Cors-Key': 'YOUR_CONNECTION_KEY', Authorization: `Bearer ${yelpKey}` },
});
// cors.dev exposes Yelp's headers and error bodies, so both are readable here
const text = await response.text();
if (!response.ok) {
throw new Error(`${response.headers.get('X-Cors-Source')} answered ${response.status}: ${text.slice(0, 200)}`);
}
console.log('Calls left today:', response.headers.get('RateLimit-Remaining'));
for (const business of JSON.parse(text).businesses) {
console.log(`${business.name}, ${business.rating} stars, ${business.location.display_address.join(', ')}`);
}The Connection key is publishable and only works from your registered origins, but cors.dev does not hide the Yelp key: it still travels from the browser. Use cors.dev Pro for internal tools and prototypes, and a backend for production.
Limits to plan for
- Yelp quotas: The daily limit answers
429 ACCESS_LIMIT_REACHEDand resets at midnight UTC; calling too fast answers429 TOO_MANY_REQUESTS_PER_SECOND. - Search size: Up to 50 businesses per request and 240 per query with
offset. Businesses without reviews never appear. - Caching and display: Cache Yelp content for at most 24 hours; business IDs may be stored indefinitely. Show the Yelp logo linked to Yelp, use Yelp-branded stars next to the review count, link ratings and excerpts back to Yelp and never blend Yelp ratings with other sources.
- cors.dev: The free tier allows GET and HEAD at about 600 requests a minute per IP. cors.dev Pro allows 500,000 requests a month at 3,000 requests a minute per account and 100 in flight. Both cap responses at 6 MiB with a 30-second upstream deadline.
Good questions.
Does the Yelp API support CORS?
Yes, with gaps. api.yelp.com answers preflights with Access-Control-Allow-Origin: *, allows Authorization, and sends the same headers on its 401 errors. Its 400 VALIDATION_ERROR responses carry none, so a missing or malformed key or a bad parameter shows up as a CORS error.
Why does api.yelp.com return 403 with an empty body?
That 403 comes from Yelp's Varnish edge, which refused your network or client before the request reached the API. Our cloud machine got it on every request, even from Chromium, while Chromium on a residential connection did not. Try another network before you change the key.
Is the Yelp API free?
No. Yelp ended free access in July 2024, and plans now run from $229 a month for Base to $643 for Premium. A 30-day trial gives 5,000 calls for evaluation only and needs a business email.
Can I keep my Yelp key in frontend code?
Only in internal tools or prototypes where you trust everyone who opens the page. Anyone with DevTools can copy a key used in browser JavaScript, with or without cors.dev, and spend your paid calls. Production apps should call Yelp from their own backend.
Does the free cors.dev proxy work with the Yelp API?
Not for real searches: it refuses the Authorization header with 400 header_not_allowed. Without a key it still shows Yelp's hidden validation errors. To forward the key, use cors.dev Pro with an X-Cors-Key header.