Google and Bing · 190+ countries · organic results
Search results, as JSON you can parse.
One GET request returns ranked organic results with position, title, URL, domain and snippet. No headless browsers, no proxy pool to babysit, no HTML to scrape.
Live call to the public API. Five demo searches a minute per visitor, no key required.
What you get
A small API surface that does the annoying part
Two endpoints, one response shape, and the engine-side mess handled behind them.
Organic results only
Every response is the same normalised array: position, title, url, domain, snippet. No ad blocks, no knowledge panels, no per-engine HTML quirks leaking into your parser.
Two targets, one contract
google_search and bing_search answer with an identical response shape. Switch engines with a query parameter instead of a second integration.
Country-level targeting
190+ countries, from us and gb to br, ng and jp. Each geo resolves its own locale and its own exit country, so results read the way they do on the ground.
Cache you control
Responses are cached and served back on a hit. Send max_age to say how fresh a result has to be, or max_age=0 to force a live fetch on this call.
Bulk in one request
POST up to 50 queries together and get 50 answers back in the same call. Per-row overrides for target, geo and locale; a bad row fails on its own without taking the batch down.
Nothing to poll
Both endpoints are synchronous. The results are in the response you already have, so there is no job id to store, no queue to drain and no webhook to receive.
Coming soon
Search from inside your agent.
Coming soonAn MCP server for SerpResult is on the way. Point Claude Code, Cursor, Codex or any MCP client at it and your agent gets ranked organic results from Google and Bing as a tool call, with the same response shape the HTTP API returns.
$ claude mcp add serpresult -- npx @serpresult/mcp
✓ Added MCP server serpresult
$ claude
> search google for "best noise cancelling headphones" in gb
⚙ serp_search · target=google_search · geo=gb · num=5
✓ 5 organic results in 812 ms
$
-
One tool, both engines. serp_search takes query, target and geo and returns the organic array.
-
Same contract as HTTP. Position, title, url, domain, snippet, nothing agent-specific to learn.
-
Your key, your credits. The server authenticates with the same sk_live key and spends the same credits.
Claude Code · Cursor · Codex · any MCP client
How it works
Three steps from signup to parsed results
- 01
Get a key
Sign up and mint a key from the dashboard. Live keys hit the real engines, test keys let you wire the integration up first.
Authorization: Bearer sk_live_…Create an account → - 02
Call /v1/query
One GET with the query, the target and the country. Everything else has a default, so the shortest useful call is a single parameter.
GET /v1/query?query=…&target=google_search&geo=usRead the reference → - 03
Parse results.organic
Ranked rows, already deduplicated and normalised. Read position, title, url, domain and snippet and move on with your day.
results.organic[0].url
The whole integration
One request in, one predictable object out
Nothing to install. Any HTTP client you already have is the SDK.
curl -G "https://api.serpresult.com/v1/query" \
-H "Authorization: Bearer $SERPRESULT_KEY" \
--data-urlencode "query=best noise cancelling headphones" \
--data-urlencode "target=google_search" \
--data-urlencode "geo=us" \
--data-urlencode "num=5"import os, requests
response = requests.get(
"https://api.serpresult.com/v1/query",
headers={"Authorization": f"Bearer {os.environ['SERPRESULT_KEY']}"},
params={
"query": "best noise cancelling headphones",
"target": "google_search",
"geo": "us",
"num": 5,
},
timeout=30,
)
response.raise_for_status()
for row in response.json()["results"]["organic"]:
print(row["position"], row["domain"], row["url"])const url = new URL("https://api.serpresult.com/v1/query");
url.search = new URLSearchParams({
query: "best noise cancelling headphones",
target: "google_search",
geo: "us",
num: "5",
}).toString();
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.SERPRESULT_KEY}` },
});
if (!response.ok) throw new Error(`Search failed: ${response.status}`);
const { results } = await response.json();
for (const row of results.organic) {
console.log(row.position, row.domain, row.url);
} {
"id": "qry_9f3c1a7d4b6e4f2a8c05d71e2b93fa60",
"target": "google_search",
"query": "best noise cancelling headphones",
"geo": "us",
"locale": "en-us",
"page": 1,
"results": {
"organic": [
{
"position": 1,
"title": "The 4 Best Noise-Cancelling Headphones of 2026",
"url": "https://www.nytimes.com/wirecutter/reviews/best-noise-cancelling-headphones/",
"domain": "nytimes.com",
"snippet": "We tested dozens of pairs to find the ones that block the most noise without wrecking the sound."
},
{
"position": 2,
"title": "Best Noise Cancelling Headphones - RTINGS.com",
"url": "https://www.rtings.com/headphones/reviews/best/noise-cancelling",
"domain": "rtings.com",
"snippet": "Our recommendations are based on measured isolation performance across several frequency ranges."
}
]
},
"info": {
"cached": false,
"fetched_at": "2026-09-08T09:14:22.517Z",
"latency_ms": 764,
"credits": 1,
"request_id": "0d5f2a91-6c3e-4b18-9a72-31f8c4de5b07"
}
} Stop maintaining a scraper.
Create an account, mint a key, and have parsed organic results in your code before the next standup.