GuidesSEARCH APIS
SerpApi from browser JavaScript
SerpApi answers browser requests without an Access-Control-Allow-Origin header and its preflight returns 404, so fetch() from a page fails even with a valid key. Because the key travels in the api_key query parameter, the free proxy at https://proxy.cors.dev/ carries the request as-is: no signup, GET and HEAD, about 600 requests a minute per IP. We tested 4 SerpApi endpoints on 28 October 2026, and the free SerpApi plan gives 250 searches a month.
Why fetch() to SerpApi fails in browsers
On 28 October 2026 we sent GET https://serpapi.com/search.json?engine=google&q=cors&api_key=<invalid key> with Origin: https://example.com, the header a browser page sends. SerpApi answered 401 with a 97-byte JSON error and no Access-Control-Allow-Origin header. The keyless locations.json answered 200 without that header too, so successful responses are blocked the same way and fetch() rejects with a CORS error.
An OPTIONS request to the same URL answered 404 without CORS headers. A plain GET needs no preflight, but any custom header such as Authorization makes the browser send one first, and that preflight fails.
SerpApi's official package, serpapi on npm (version 2.2.1, getJson({ engine, q, api_key })), documents Node.js 7.10.1 and newer and Deno, not browsers. Run it on a server.
Call SerpApi through the free proxy
The same test URL prefixed with https://proxy.cors.dev/ came back with the identical 401 and 97-byte body, now with Access-Control-Allow-Origin set to the page origin. That proves the api_key query parameter reaches SerpApi and the answer reaches your code.
- Build the URL
Put
engine,qandapi_keyintoURLSearchParamsonhttps://serpapi.com/search.json. - Prefix the proxy
Prepend
https://proxy.cors.dev/and callfetch()without custom headers. - Check both error layers
Read
X-Cors-Errorfor proxy refusals, then SerpApi'serrorfield, before you useorganic_results.
const PROXY = 'https://proxy.cors.dev/';
async function serpSearch(query, apiKey) {
const params = new URLSearchParams({ engine: 'google', q: query, api_key: apiKey });
const response = await fetch(PROXY + 'https://serpapi.com/search.json?' + params);
const proxyError = response.headers.get('X-Cors-Error');
if (proxyError) throw new Error(`cors.dev refused the request: ${proxyError}`);
const data = await response.json();
if (data.error) throw new Error(`SerpApi ${response.status}: ${data.error}`);
return data.organic_results ?? [];
}
const apiKey = localStorage.getItem('serpapi_key');
const results = await serpSearch('cors proxy', apiKey);
for (const { position, title, link } of results) {
console.log(position, title, link);
}The free proxy needs no signup and no key. It allows GET and HEAD, about 600 requests a minute per IP, responses up to 6 MiB and a 30-second upstream deadline, with no caching. An Authorization header gets 400 with X-Cors-Error: header_not_allowed, and SerpApi never needs one.
cors.dev Pro ($5 a month for 500,000 requests, 7-day trial with 1,000 requests, no card) adds POST and credential headers such as X-Subscription-Token, from origins you register. SerpApi does not need it, because its key travels in the URL.
Where your SerpApi key should live
A key in browser code, in a URL or a header, is readable by anyone who opens the page or its network tab. cors.dev does not hide it, and whoever holds your key can spend your searches.
- Prototypes and internal tools: Your own key in the page is acceptable when only you or your team load it.
- Bring-your-own-key apps: Each user pastes their own SerpApi key, for example into
localStorage, and spends their own searches. - Public products on your key: Call SerpApi from your backend and send only the results to the page.
SerpApi endpoints tested in the browser
| Endpoint | Needs a key | Direct fetch() | Through the free proxy |
|---|---|---|---|
| search.json, engine=google | Yes | 401 without CORS headers, blocked | 401 with CORS headers, 97 bytes |
| search.json, engine=google_autocomplete | Yes | 401 without CORS headers, blocked | 401 with CORS headers |
| locations.json | No | 200 without CORS headers, blocked | 200 with CORS headers, 1,196 bytes |
| account.json | Yes | 401 without CORS headers, blocked | 401 with CORS headers |
locations.json?q=Austin&limit=3 needs no key, so it works through the free proxy with nothing to protect. Each location has canonical_name, country_code, target_type, reach and gps. Use it to look up a valid location value for searches.
const params = new URLSearchParams({ q: 'Austin', limit: '3' });
const response = await fetch('https://proxy.cors.dev/https://serpapi.com/locations.json?' + params);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.headers.get('X-Cors-Error') ?? ''}`);
}
const locations = await response.json();
for (const { canonical_name, target_type, reach } of locations) {
console.log(canonical_name, target_type, reach);
}
// Austin,Texas,United States City 6440000SerpApi pricing and the free plan
We read SerpApi's pricing page on 28 October 2026. Plans are month-to-month and you can cancel anytime.
| Plan | Price per month | Searches per month | Searches per hour |
|---|---|---|---|
| Free | $0 | 250 | 50 |
| Starter | $25 | 1,000 | 200 |
| Developer | $75 | 5,000 | 1,000 |
| Production | $150 | 15,000 | 3,000 |
| Big Data | $275 | 30,000 | 6,000 |
Bigger plans: Searcher at $725 for 100,000 searches, Volume at $1,475 for 250,000, Infrastructure at $2,750 for 500,000, and Cloud plans from $3,750 for 1 million up to $69,375 for 35 million a month.
Only successful searches count; cached and errored searches are not charged. SerpApi refunds within 7 days unless more than 20% of the searches were used.
SerpApi alternatives for browser apps
| API | Free allowance | Where the key goes | Browser CORS | cors.dev route |
|---|---|---|---|---|
| SerpApi | 250 searches a month, 50 an hour | api_key query parameter | None, OPTIONS answers 404 | Free proxy |
| Serper | 2,500 queries, no card | X-API-KEY header | Access-Control-Allow-Origin: * | None, call it directly |
| Brave Search API | $5 credit a month, about 1,000 requests | X-Subscription-Token header | None, OPTIONS answers 405 | cors.dev Pro |
| Google Custom Search JSON API | 100 queries a day, existing customers only | key query parameter | Echoes the page origin | None, call it directly |
| Tavily | 1,000 credits a month | Authorization header | Access-Control-Allow-Origin: * | None, call it directly |
Serper, another Google results API, answered its preflight with 204, Access-Control-Allow-Origin: * and X-API-KEY in the allowed headers, and an invalid key got a 403 carrying the same CORS header. Call it directly; the key is just as visible in the page. It gives 2,500 free queries without a card, and paid credits run from $50 for 50,000 queries to $3,750 for 12.5 million ($0.30 per 1,000), valid for 6 months.
Brave runs its own index but sends no CORS headers and needs the X-Subscription-Token header, which only cors.dev Pro forwards. Google closed the Custom Search JSON API to new customers and ends it on 1 January 2027.
Good questions.
Can I use the serpapi npm package in the browser?
Its README documents Node.js 7.10.1 and newer and Deno, not browsers, so run serpapi 2.2.1 on a server. In the browser, call search.json with fetch() through https://proxy.cors.dev/ instead.
Why does the cors.dev proxy return 400 header_not_allowed for my SerpApi request?
The free proxy refuses credential headers such as Authorization or X-Api-Key with 400 and X-Cors-Error: header_not_allowed. SerpApi reads the key from the api_key query parameter, so drop the header and keep the key in the URL.
Does a CORS proxy hide my SerpApi key?
No. Anything in browser code, URL or header, is visible to every visitor in the network tab, and cors.dev does not change that. Use your own key in the browser only for prototypes and internal tools, and call SerpApi from your backend for a public product.
Do failed searches count against my SerpApi plan?
No. SerpApi counts only successful searches; cached and errored searches are not charged. The free plan allows 250 successful searches a month and 50 an hour.
Do I need cors.dev Pro to call SerpApi?
No. SerpApi uses GET with the key in the query string, which the free proxy forwards as-is, with no signup and about 600 requests a minute per IP. Pro ($5 a month for 500,000 requests) is for APIs that need POST or a key header, such as Brave's X-Subscription-Token.