SERP API
Scrape Google and Bing search engine results pages with full ad extraction, organic results, rich snippets, and structured data. Browser-rendered for complete ad visibility.
Ad Intelligence Endpoint
/v1/search endpoint (lightweight keyword search at $0.001/query), this endpoint renders the full SERP page to extract paid ads, shopping results, and rich SERP features.Overview
Query
POST a search query to /api/v1/serp. Specify engine, geo, device, and ad extraction options.
Render
A real browser loads the SERP page with stealth fingerprinting, ensuring ads render exactly as they would for a human user.
Extract
Structured data is extracted: organic results, paid ads (with placement, advertiser, extensions), featured snippets, knowledge panels, and more.
| Feature | /v1/search | /api/v1/serp |
|---|---|---|
| Organic results | Yes | Yes |
| Paid ad extraction | No | Yes |
| Shopping ads | No | Yes |
| Brand classification | No | Yes |
| Session persistence | No | Yes |
| CAPTCHA solving | No | Yes |
| Rich results (PAA, KP) | Limited | Full |
| Cost per query | $0.001 | $0.004 |
Quick Start
Get your first SERP result in seconds. This example queries Google for "crm software" with ad extraction enabled.
curl -X POST https://api.alterlab.io/api/v1/serp \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "crm software",
"include_ads": true,
"country": "US"
}'POST /api/v1/serp
/api/v1/serpExecute a SERP scrape with browser rendering. Returns organic results, paid ads, and rich result data. Supports inline wait (up to 30s) or async polling.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| query | string | Required | Search query (1-500 characters). The search term to look up on the target engine. |
| search_engine | "google" | "bing" | Optional | Target search engine. Google is the primary target for ad extraction.Default: google |
| device | "desktop" | "mobile" | Optional | Device type to emulate. Affects SERP layout and ad placements. Desktop typically shows more ads.Default: desktop |
| country | string | Optional | ISO 3166-1 alpha-2 country code for geo-targeted results (e.g. "US", "GB", "DE"). Routes through a proxy in the specified country. |
| city | string | Optional | City name for local ad targeting (e.g. "London", "New York"). Used with country for precise geo-targeting. |
| language | string | Optional | Language code for results and browser locale (e.g. "en", "de", "fr"). |
| num_results | integer | Optional | Number of organic results to request (1-100). Google typically serves 10 per page.Default: 10 |
| page | integer | Optional | Result page number (1-10). Page 2 = results 11-20.Default: 1 |
| include_ads | boolean | Optional | Extract ad data from the SERP. When false, only organic results are returned (faster). The primary use case for this endpoint.Default: true |
| brand_domain | string | Optional | Brand domain for ad classification (e.g. "wayfair.com"). Each ad gets is_brand_ad: true/false based on whether its display domain matches. |
| resolve_redirects | boolean | Optional | Follow ad click-through redirect chains to resolve final landing URLs. Adds ~200-500ms per ad.Default: false |
| session_id | string | Optional | Reuse a long-lived browser session for sticky IP and fingerprint continuity across queries. |
| solve_captchas | boolean | Optional | Automatically solve CAPTCHAs when Google presents a challenge. Adds latency but avoids errors.Default: true |
Response Schema
The response contains structured data extracted from the rendered SERP page. All fields are populated based on what the search engine returns.
{
"serp_id": "serp_abc123def456",
"query": "crm software",
"search_engine": "google",
"device": "desktop",
"country": "US",
"language": "en",
"session_id": null,
"organic_results": [
{
"position": 1,
"url": "https://www.salesforce.com/crm/",
"title": "CRM Software from Salesforce - Customer Relationship Management",
"snippet": "Salesforce CRM helps businesses of all sizes manage customer relationships...",
"displayed_url": "salesforce.com/crm",
"date_published": null,
"sitelinks": [
{ "title": "Pricing", "url": "https://www.salesforce.com/crm/pricing/" },
{ "title": "Free Trial", "url": "https://www.salesforce.com/form/signup/" }
]
}
],
"organic_count": 10,
"ads": [
{
"position": 1,
"placement": "top",
"ad_type": "text",
"headline": "HubSpot CRM - Free CRM Software",
"headline_2": "Start Growing Better Today",
"headline_3": null,
"description": "Get started with free CRM tools for your whole team. No credit card required.",
"description_2": null,
"display_url": "hubspot.com/crm/free",
"landing_url": null,
"tracking_url": "https://www.google.com/aclk?sa=l&ai=...",
"advertiser_name": "HubSpot",
"extensions": [
{ "type": "sitelink", "text": "Free Tools", "url": "https://hubspot.com/free" },
{ "type": "callout", "text": "No Credit Card Required", "url": null }
],
"is_brand_ad": false,
"is_sponsored": true,
"price": null,
"merchant": null,
"image_url": null,
"rating": null,
"review_count": null
}
],
"ads_count": 4,
"ads_by_placement": { "top": 3, "bottom": 1, "sidebar": 0, "shopping": 0 },
"featured_snippet": {
"content": "CRM software helps businesses manage customer interactions and data...",
"source_url": "https://example.com/what-is-crm",
"source_title": "What is CRM? - Complete Guide",
"snippet_type": "text"
},
"knowledge_panel": null,
"people_also_ask": [
{
"question": "What is the best CRM software for small businesses?",
"answer": "Popular CRM options for small businesses include HubSpot CRM (free tier)...",
"source_url": "https://example.com/best-crm"
}
],
"related_searches": [
"best crm software 2026",
"free crm software",
"crm software for small business"
],
"did_you_mean": null,
"local_results": [],
"video_results": [],
"news_results": [],
"image_results": [],
"shopping_results": [],
"estimated_total_results": 2340000000,
"cost_breakdown": {
"base_cost_microcents": 4000,
"ad_extraction_cost_microcents": 0,
"captcha_solve_cost_microcents": 0,
"total_cost_microcents": 4000,
"tier_used": "4"
},
"captcha_stats": null
}Ad Result Schema
Each ad in the ads array contains the full ad creative with positioning, text, URLs, and extensions.
| Field | Type | Description |
|---|---|---|
| position | integer | Ad position within its placement group (1-indexed) |
| placement | "top" | "bottom" | "sidebar" | "shopping" | Where the ad appears on the SERP page |
| ad_type | "text" | "shopping" | "local" | "video" | "app" | Ad format type |
| headline | string | Primary ad headline text |
| headline_2, headline_3 | string | null | Additional headlines if present |
| description | string | null | Ad description / body text |
| display_url | string | URL displayed in the ad (breadcrumb-style) |
| landing_url | string | null | Final landing URL after redirect resolution (requires resolve_redirects: true) |
| tracking_url | string | null | Raw click-through URL from Google's tracking redirect |
| advertiser_name | string | null | Verified advertiser name shown by Google |
| extensions | array | Sitelinks, callouts, phone numbers, and other extensions |
| is_brand_ad | boolean | null | Whether the ad's display domain matches the brand_domain. Null when brand_domain not provided. |
| price | string | null | Product price — shopping ads only |
| merchant | string | null | Merchant name — shopping ads only |
Organic Result Schema
| Field | Type | Description |
|---|---|---|
| position | integer | 1-indexed position on the page |
| url | string | Result URL |
| title | string | Result title |
| snippet | string | null | Result snippet/description |
| displayed_url | string | null | URL as displayed in SERP (breadcrumb-style) |
| date_published | string | null | Date shown next to the result |
| sitelinks | array | Sitelinks under this result (title + url) |
Rich Results
The response includes all rich SERP features when present on the page.
| Field | Type | Description |
|---|---|---|
| featured_snippet | object | null | Featured snippet / answer box (content, source_url, snippet_type) |
| knowledge_panel | object | null | Knowledge panel (title, entity_type, description, facts) |
| people_also_ask | array | PAA questions (question, answer, source_url) |
| related_searches | array of strings | Related search suggestions |
| local_results | array | Local pack results (name, address, rating, phone) |
| video_results | array | Video carousel (title, url, source, duration) |
| news_results | array | News carousel (title, url, source, date) |
| shopping_results | array | Organic shopping carousel (title, price, merchant) |
Cost Breakdown
Every response includes a cost_breakdown object showing exactly what was charged.
| Field | Type | Description |
|---|---|---|
| base_cost_microcents | integer | Base cost including browser rendering (4000 = $0.004) |
| ad_extraction_cost_microcents | integer | Additional cost for redirect resolution |
| captcha_solve_cost_microcents | integer | CAPTCHA solving cost (non-zero when a challenge was solved) |
| total_cost_microcents | integer | Total cost for this request |
| tier_used | string | Scraping tier used (always "4" for SERP) |
Async Polling
If the SERP scrape takes longer than 30 seconds (e.g., due to CAPTCHA solving), the response includes a serp_id with status "processing". Poll for results using the status endpoint.
/api/v1/serp/{serp_id}Poll for async SERP results. Returns the full SerpResponse when status is 'completed'.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| serp_id | string | Required | The serp_id from the initial POST /api/v1/serp response. |
import time
import requests
# Initial request
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"query": "insurance quotes", "solve_captchas": True},
)
data = resp.json()
# If processing, poll for results
if data.get("status") == "processing":
serp_id = data["serp_id"]
while True:
time.sleep(2)
status = requests.get(
f"https://api.alterlab.io/api/v1/serp/{serp_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
).json()
if status["status"] == "completed":
data = status["result"]
break
elif status["status"] == "failed":
raise Exception(f"SERP scrape failed: {status.get('error')}")Batch SERP
Submit up to 50 SERP queries for parallel processing. Ideal for monitoring multiple keywords or comparing results across markets.
Batch Request
/api/v1/serp/batchSubmit a batch of SERP queries for async parallel processing. Each query can override batch-level defaults.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| queries | array | Required | Array of query objects (1-50 items). Each item has: query (required), device, country, city, language, include_ads, brand_domain. |
| search_engine | "google" | "bing" | Optional | Default search engine for all queries.Default: google |
| device | "desktop" | "mobile" | Optional | Default device type for all queries.Default: desktop |
| country | string | Optional | Default country for all queries. |
| include_ads | boolean | Optional | Default ad extraction setting.Default: true |
| brand_domain | string | Optional | Default brand domain for classification. |
| session_id | string | Optional | Reuse a session for all queries in this batch. |
| webhook_url | string | Optional | Webhook URL for result delivery. Results are POSTed as each query completes. |
import requests
resp = requests.post(
"https://api.alterlab.io/api/v1/serp/batch",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"queries": [
{"query": "crm software", "country": "US"},
{"query": "crm software", "country": "GB"},
{"query": "crm software", "country": "DE"},
],
"include_ads": True,
"brand_domain": "salesforce.com",
},
)
batch = resp.json()
print(f"Batch ID: {batch['batch_id']}")
cost = batch['estimated_cost_microcents'] / 1_000_000
print(f"Estimated cost: {cost:.4f}")Poll Batch Status
/api/v1/serp/batch/{batch_id}Poll for batch progress and results. Returns per-query status and results as they complete.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| batch_id | string | Required | Batch ID from the POST /api/v1/serp/batch response. |
{
"batch_id": "batch_xyz789",
"status": "completed",
"total_queries": 3,
"completed": 3,
"failed": 0,
"total_cost_microcents": 12000,
"items": [
{
"index": 0,
"query": "crm software",
"status": "completed",
"result": { "...full SerpResponse..." },
"error": null
}
]
}Configuration Guide
Engine Selection
The SERP API supports Google and Bing. Google is the primary target with full ad extraction support.
| Engine | Ad Extraction | Shopping Ads | Rich Results | Geo-Targeting |
|---|---|---|---|---|
| Full (top, bottom, sidebar) | Yes | Full (PAA, KP, snippets, local, video, news) | Country + city | |
| Bing | Top + sidebar | Limited | Basic (snippets, PAA) | Country only |
Geo-Targeting
Control where your search originates from using country and city parameters. The request is routed through a proxy in the specified location.
# Compare ads across markets
markets = [
{"country": "US", "language": "en"},
{"country": "GB", "language": "en"},
{"country": "DE", "language": "de"},
{"country": "FR", "language": "fr"},
]
for market in markets:
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"query": "project management software", **market, "include_ads": True},
)
data = resp.json()
print(f"{market['country']}: {data['ads_count']} ads found")City requires Country
city parameter only works when country is also provided. Without a country, city targeting is ignored.Ad Extraction
When include_ads: true (default), the SERP API extracts all paid ad placements. Use brand_domain to automatically classify ads as brand vs. competitor.
# Monitor who's bidding on your brand keywords
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"query": "salesforce crm",
"brand_domain": "salesforce.com",
"include_ads": True,
"resolve_redirects": True, # Get actual landing URLs
},
)
data = resp.json()
brand_ads = [ad for ad in data["ads"] if ad["is_brand_ad"] is True]
competitor_ads = [ad for ad in data["ads"] if ad["is_brand_ad"] is False]
print(f"Brand ads: {len(brand_ads)}")
print(f"Competitor ads: {len(competitor_ads)}")
for ad in competitor_ads:
print(f" - {ad['advertiser_name'] or ad['display_url']}: {ad['headline']}")Ad Placement Types
top— Above organic results (highest visibility, highest CPC)bottom— Below organic resultssidebar— Right-side panel (desktop only)shopping— Shopping carousel (product listings with images/prices)
Session Management
Use session_id to maintain a persistent browser session across multiple queries. The same IP, cookies, and browser fingerprint are reused — essential for pagination and consistent results.
# Paginate through results on the same session
session_id = "my-research-session-001"
all_results = []
for page in range(1, 4): # Pages 1-3
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"query": "best project management tools",
"session_id": session_id,
"page": page,
"include_ads": True,
},
)
data = resp.json()
all_results.extend(data["organic_results"])
print(f"Page {page}: {len(data['organic_results'])} results, {data['ads_count']} ads")
print(f"Total organic results collected: {len(all_results)}")Session Lifespan
CAPTCHA Handling
Google may present reCAPTCHA challenges for automated queries. With solve_captchas: true (default), challenges are solved transparently. The response includes captcha_stats when a solve occurred.
{
"captcha_stats": {
"captcha_encountered": true,
"captcha_solved": true,
"solve_time_ms": 4200,
"solver_provider": "capsolver"
}
}Use Cases
Basic SERP Scraping
Get organic results for a keyword with full metadata.
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"query": "best laptops 2026", "include_ads": False},
)
for result in resp.json()["organic_results"]:
print(f"{result['position']}. {result['title']}")
print(f" {result['url']}")
print(f" {result['snippet']}")
print()Ad Intelligence
Extract all advertisers bidding on a keyword with placement data.
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"query": "project management software",
"include_ads": True,
"resolve_redirects": True,
},
)
data = resp.json()
print(f"Ads by placement: {data['ads_by_placement']}")
for ad in data["ads"]:
print(f"[{ad['placement']}#{ad['position']}] {ad['headline']}")
print(f" Advertiser: {ad['advertiser_name']}")
print(f" Display: {ad['display_url']}")
print(f" Landing: {ad['landing_url']}")
print(f" Extensions: {len(ad['extensions'])}")
print()Brand vs. Competitor Monitoring
Detect who is advertising on your brand keywords and classify brand vs. competitor placements.
keywords = ["wayfair furniture", "wayfair couches", "wayfair sale"]
competitor_data = []
for kw in keywords:
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"query": kw,
"brand_domain": "wayfair.com",
"include_ads": True,
},
)
data = resp.json()
competitors = [ad for ad in data["ads"] if ad["is_brand_ad"] is False]
competitor_data.append({"keyword": kw, "competitors": competitors})
# Report on competitor activity
for item in competitor_data:
print(f"\nKeyword: {item['keyword']}")
for ad in item["competitors"]:
print(f" Competitor: {ad['display_url']} - {ad['headline']}")Geo-Targeted Comparison
Compare SERP results and ad landscape across different markets.
import requests
markets = ["US", "GB", "DE", "JP", "BR"]
query = "cloud computing services"
for country in markets:
resp = requests.post(
"https://api.alterlab.io/api/v1/serp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"query": query, "country": country, "include_ads": True},
)
data = resp.json()
top_organic = data["organic_results"][0]["title"] if data["organic_results"] else "N/A"
print(f"{country}: {data['organic_count']} organic, {data['ads_count']} ads")
print(f" Top result: {top_organic}")
print(f" Ads breakdown: {data['ads_by_placement']}")Pricing
| Component | Cost | Notes |
|---|---|---|
| Base SERP scrape | $0.004 / query | Includes browser rendering (Tier 4), ad extraction, all SERP features |
| CAPTCHA solving | Bundled | Included in base cost when solve_captchas is enabled |
| Redirect resolution | Bundled | Included when resolve_redirects is enabled |
| Batch (up to 50 queries) | $0.004 x N | Same per-query pricing, parallel execution |
Cost Transparency
cost_breakdown object showing the exact cost charged. Monitor your spend in real-time via the total_cost_microcents field (1,000,000 microcents = $1).Example cost calculations
- 1,000 SERP queries/day = $4.00/day = $120/month
- 10,000 SERP queries/day = $40.00/day = $1,200/month
- 100,000 SERP queries/day = $400.00/day = $12,000/month
Error Codes
| Code | Meaning | Resolution |
|---|---|---|
| 400 | Invalid request parameters | Check query length, country code format, parameter types |
| 401 | Invalid or missing API key | Verify X-API-Key header is present and valid |
| 402 | Insufficient balance | Add funds to your account. Each SERP query costs $0.004. |
| 422 | Validation error | Check the error detail — often an unsupported country code or invalid query |
| 429 | Rate limited | Back off and retry. Use batch endpoint for high-volume queries. |
| 500 | Internal error | Retry after a few seconds. If persistent, check status page. |
| 503 | SERP scrape failed (blocked/timeout) | The target search engine blocked the request. Retry — a different proxy/fingerprint will be used. |