Expanded Limits, Across all tiers - Our Gift to you! Learn more →

AlterLab
SearchResearchPricingComparePlaygroundBlogDocsChangelog
    AlterLabAlterLab
    PricingComparePlaygroundBlogDocsChangelog
    IntroductionQuickstartInstallationYour First Request
    REST APICrawl APIMap APISearch APIResearch APINewSERP APINewExtract APIAIJob PollingAPI KeysSessions APINewEnterprise APIEnterprise
    AccountAutoAlertsAutoAuthAutoBillingAutoCrawlAutoExtractAutoIntegrationsAutoKeysAutoMapAutoMonitorsAutoOrganizationsAutoSchedulesAutoScrapeAutoSearchAutoSessionsAutoUser WebhooksAutoV1 EndpointsAutoWebhooksAuto
    OverviewPythonNode.js
    JavaScript RenderingBrowser ActionsOutput FormatsPDF & OCRCachingWebhooksJSON Schema FilteringWebSocket Real-TimeBring Your Own ProxyProSticky SessionsProAuthenticated ScrapingNewHTTP Methods & BodiesNewStructured ExtractionAIWeb SearchAgent and RAG GroundingResearch JobsAgent RunsSite MappingWeb CrawlingBatch ScrapingSchedulerChange DetectionCloud Storage ExportSpend LimitsOrganizations & TeamsAlerts & NotificationsExtraction ProfilesAIBYOK ExtractionAIOAuth2 Machine-to-MachineSupport & TicketsUnsupported Targets
    Structured ExtractionAIE-commerce ScrapingNews MonitoringPrice MonitoringMulti-Page CrawlingMonitoring DashboardAI Agent / MCPMCPAI Research AgentAISite CrawlingData Pipeline to Cloud
    E-commerceLead GenerationChange MonitoringRAG & AI PipelinesAIResearch
    PricingRate LimitsError CodesChangelogVersioning
    From FirecrawlFrom ApifyFrom ScrapingBee / ScraperAPIFrom Crawl4AIFrom SpiderSearch to SERPFirecrawl v0 API ReferenceLegacy
    OverviewMCP ServerAIn8n NodeLangChainAICrewAIAILlamaIndexAISupabaseChrome ExtensionSoon
    PlaygroundPricingStatus

    Up to 5,000 free requests · No credit card

    API Reference
    New

    SERP API

    One endpoint for Google, Bing and DuckDuckGo search results: organic results, ads, rich snippets and structured data, selected per request with search_engine (alias engine).

    Single search surface

    /v1/serp is the single search endpoint. The older /v1/search and /v1/search/batch endpoints still work as deprecated aliases with identical billing; their responses carry a Deprecation header and a link to this endpoint. See the migration guide. This endpoint defaults to google ($0.004/query) when search_engine is omitted; pick duckduckgo ($0.001/query) for lightweight keyword search. Responses are structured JSON; scrape_results, domain and time_range are currently served only by the /v1/search alias.

    Overview

    1

    Query

    POST a search query to /api/v1/serp. Specify engine, geo, device, and ad extraction options.

    2

    Render

    The SERP page is rendered in a browser so ads and rich features appear as they do for a person using the engine.

    3

    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 resultsYesYes
    Paid ad extractionNoYes
    Shopping adsNoYes
    Brand classificationNoYes
    Session persistenceNoYes
    Challenge resolutionNoYes
    Rich results (PAA, KP)LimitedFull
    Cost per query$0.001$0.004

    Schema Versioning

    Every response carries a schema_version field (currently "1"). It changes only on a breaking change; additive fields never bump it. Removals follow a minimum 180-day deprecation window announced in the schema changelog.

    Quick Start

    Get your first SERP result in seconds. This example queries Google for "crm software" with ad extraction enabled.

    Bash
    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

    POST
    /api/v1/serp

    Execute 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

    NameTypeRequiredDescription
    querystring
    Required
    Search query (1-500 characters). The search term to look up on the target engine.
    search_engine"google" | "bing" | "duckduckgo"OptionalTarget search engine (alias: engine). Ad extraction is available on Google and Bing.Default: google
    device"desktop" | "mobile"OptionalDevice type to emulate. Affects SERP layout and ad placements. Desktop typically shows more ads.Default: desktop
    countrystringOptionalISO 3166-1 alpha-2 country code for geo-targeted results (e.g. "US", "GB", "DE"). Results are localized to that country.
    citystringOptionalCity name for local ad targeting (e.g. "London", "New York"). Used with country for precise geo-targeting.
    languagestringOptionalLanguage code for results and browser locale (e.g. "en", "de", "fr").
    num_resultsintegerOptionalNumber of organic results to request (1-100). Google typically serves 10 per page.Default: 10
    pageintegerOptionalResult page number (1-10). Page 2 = results 11-20.Default: 1
    include_adsbooleanOptionalExtract ad data from the SERP. When false, only organic results are returned (faster). The primary use case for this endpoint.Default: true
    brand_domainstringOptionalBrand domain for ad classification (e.g. "wayfair.com"). Each ad gets is_brand_ad: true/false based on whether its display domain matches.
    resolve_redirectsbooleanOptionalFollow ad click-through redirect chains to resolve final landing URLs. Adds ~200-500ms per ad.Default: false
    session_idstringOptionalReuse a long-lived browser session across queries for consistent results.
    solve_captchasbooleanOptionalAutomatically 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.

    JSON
    {
      "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.

    FieldTypeDescription
    positionintegerAd 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
    headlinestringPrimary ad headline text
    headline_2, headline_3string | nullAdditional headlines if present
    descriptionstring | nullAd description / body text
    display_urlstringURL displayed in the ad (breadcrumb-style)
    landing_urlstring | nullFinal landing URL after redirect resolution (requires resolve_redirects: true)
    tracking_urlstring | nullRaw click-through URL from the search engine's tracking redirect
    advertiser_namestring | nullVerified advertiser name shown by Google
    extensionsarraySitelinks, callouts, phone numbers, and other extensions
    is_brand_adboolean | nullWhether the ad's display domain matches the brand_domain. Null when brand_domain not provided.
    pricestring | nullProduct price — shopping ads only
    merchantstring | nullMerchant name — shopping ads only

    Organic Result Schema

    FieldTypeDescription
    positioninteger1-indexed position on the page
    urlstringResult URL
    titlestringResult title
    snippetstring | nullResult snippet/description
    displayed_urlstring | nullURL as displayed in SERP (breadcrumb-style)
    date_publishedstring | nullDate shown next to the result
    sitelinksarraySitelinks under this result (title + url)

    Rich Results

    The response includes all rich SERP features when present on the page.

    FieldTypeDescription
    featured_snippetobject | nullFeatured snippet / answer box (content, source_url, snippet_type)
    knowledge_panelobject | nullKnowledge panel (title, entity_type, description, facts)
    people_also_askarrayPAA questions (question, answer, source_url)
    related_searchesarray of stringsRelated search suggestions
    local_resultsarrayLocal pack results (name, address, rating, phone)
    video_resultsarrayVideo carousel (title, url, source, duration)
    news_resultsarrayNews carousel (title, url, source, date)
    shopping_resultsarrayOrganic shopping carousel (title, price, merchant)

    Cost Breakdown

    Every response includes a cost_breakdown object showing exactly what was charged.

    FieldTypeDescription
    base_cost_microcentsintegerBase cost including browser rendering (4000 = $0.004)
    ad_extraction_cost_microcentsintegerAdditional cost for redirect resolution
    captcha_solve_cost_microcentsintegerChallenge resolution cost (non-zero when a challenge was resolved)
    total_cost_microcentsintegerTotal cost for this request
    tier_usedstringScraping 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.

    GET
    /api/v1/serp/{serp_id}

    Poll for async SERP results. Returns the full SerpResponse when status is 'completed'.

    Parameters

    NameTypeRequiredDescription
    serp_idstring
    Required
    The serp_id from the initial POST /api/v1/serp response.
    Python
    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

    POST
    /api/v1/serp/batch

    Submit a batch of SERP queries for async parallel processing. Each query can override batch-level defaults.

    Parameters

    NameTypeRequiredDescription
    queriesarray
    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" | "duckduckgo"OptionalDefault search engine for all queries.Default: google
    device"desktop" | "mobile"OptionalDefault device type for all queries.Default: desktop
    countrystringOptionalDefault country for all queries.
    include_adsbooleanOptionalDefault ad extraction setting.Default: true
    brand_domainstringOptionalDefault brand domain for classification.
    session_idstringOptionalReuse a session for all queries in this batch.
    webhook_urlstringOptionalWebhook URL for result delivery. Results are POSTed as each query completes.
    Python
    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

    GET
    /api/v1/serp/batch/{batch_id}

    Poll for batch progress and results. Returns per-query status and results as they complete.

    Parameters

    NameTypeRequiredDescription
    batch_idstring
    Required
    Batch ID from the POST /api/v1/serp/batch response.
    JSON
    {
      "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

    Choose the engine per request with search_engine. Each engine has a flat per-query price, listed below and in Pricing. Pagination uses num_results (alias num) and start (or page); the window start + num_results may not exceed the engine cap (DuckDuckGo 30, Google 100, Bing 100) or the request is rejected with 422 before any charge. device (desktop or mobile) selects the result layout.

    EnginePriceAd ExtractionShopping AdsRich ResultsGeo-Targeting
    Google$0.004Full (top, bottom, sidebar)YesFull (PAA, KP, snippets, local, video, news)Country + city
    Bing$0.004Top + sidebarLimitedBasic (snippets, PAA)Country only
    DuckDuckGo$0.001Not availableNot availableOrganic resultsCountry

    Geo-Targeting

    Control where your search originates from using country and city parameters. Results are localized to the specified location.

    Python
    # 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

    The 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.

    Python
    # 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 results
    • sidebar — 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 browser session is reused, which keeps pagination and results consistent.

    Python
    # 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

    Sessions persist as long as they are actively used. A session expires after 10 minutes of inactivity. Reusing an expired session_id transparently creates a new session.

    Challenge Resolution

    A search engine may present a verification challenge. With solve_captchas: true (default), challenges are resolved transparently. The response includes captcha_stats when a solve occurred.

    JSON
    {
      "captcha_stats": {
        "captcha_encountered": true,
        "captcha_solved": true,
        "solve_time_ms": 4200
      }
    }

    Use Cases

    Basic SERP Scraping

    Get organic results for a keyword with full metadata.

    Python
    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.

    Python
    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.

    Python
    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.

    Python
    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

    ComponentCostNotes
    DuckDuckGo query$0.001 / queryDefault engine on the deprecated /v1/search alias
    Google query$0.004 / queryDefault engine on /v1/serp; includes ad extraction and all SERP features
    Bing query$0.004 / queryIncludes ad extraction
    Challenge resolutionBundledIncluded in base cost when solve_captchas is enabled
    Redirect resolutionBundledIncluded when resolve_redirects is enabled
    Batch (up to 50 queries)Per-engine price x NSame per-query pricing, parallel execution

    Cost Transparency

    Every response includes a 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 Google or Bing queries/day = $4.00/day = $120/month
    • 10,000 Google or Bing queries/day = $40.00/day = $1,200/month
    • 100,000 Google or Bing queries/day = $400.00/day = $12,000/month

    Error Codes

    CodeMeaningResolution
    400Invalid request parametersCheck query length, country code format, parameter types
    401Invalid or missing API keyVerify X-API-Key header is present and valid
    402Insufficient balanceAdd funds to your account. Per-query prices are listed in Pricing.
    422Validation errorCheck the error detail — often an unsupported country code or invalid query
    429Rate limitedBack off and retry after the Retry-After header. Rate limits are per API key and match scrape requests; a batch counts one request per query.
    500Internal errorRetry after a few seconds. If persistent, check status page.
    503SERP scrape failed (blocked/timeout)The search engine did not return results. Retrying may succeed.
    Last updated: June 2026

    On this page