SerpHubAPI

Migrate from SerpApi

Map SerpApi parameters and response fields to SerpHubAPI and switch over with a few lines of code.

SerpHubAPI uses the parameter names you already know (q, gl, hl, num), so most SerpApi integrations move over by changing the URL, the auth header and a few field names.

Quick switch

Before (SerpApi):

import requests

r = requests.get("https://serpapi.com/search", params={
    "engine": "google",
    "q": "coffee",
    "gl": "us",
    "hl": "en",
    "num": 10,
    "api_key": SERPAPI_KEY,
})
for item in r.json().get("organic_results", []):
    print(item["position"], item["title"], item["link"])

After (SerpHubAPI):

import requests

r = requests.post("https://api.serphubapi.com/v1/search",
    headers={"X-API-Key": SERPHUBAPI_KEY},          # shapi_xxxxxxxx
    json={
        "engine": "google",
        "q": "coffee",
        "gl": "us",
        "hl": "en",
        "num": 10,
    })
for item in r.json()["results"].get("organic", []):
    print(item["position"], item["title"], item["link"])

Parameter mapping

SerpApiSerpHubAPINotes
GET https://serpapi.com/searchPOST https://api.serphubapi.com/v1/searchJSON body instead of query string
api_key (query)X-API-Key header (or Authorization: Bearer)Keys never go in the URL
qqSame. For Yandex, SerpApi uses text; we use q for every engine
engine=googleengine=google, type=web
engine=bing / engine=yandexengine=bing / engine=yandexWeb results
engine=google_news or tbm=nwsengine=google, type=news
engine=google_images or tbm=ischengine=google, type=images
engine=google_videos or tbm=videngine=google, type=videos
engine=google_shopping or tbm=shopengine=google, type=shopping
engine=google_mapsengine=google, type=mapsResults in results.places
engine=google_scholarengine=google, type=scholar
engine=google_autocompleteengine=google, type=autocompleteResults in results.suggestions
glglSame
hlhlSame
numnum1–100
start (offset)page (1-based)page = start / num + 1
tbs=qdr:d / qdr:w / qdr:m / qdr:ytime_range=day / week / month / yearOther tbs values have no equivalent
no_cache=truemode=live (default)
default (cached results allowed)mode=cache_firstHalf price; free on a cache hit
output=html—Use format=markdown for a text rendering
location, uule, google_domain, device—Not supported; use gl / hl
GET /searches/{id}.jsonGET /v1/searches/{id}Archive kept 30 days
GET /account.jsonGET /v1/account

Response mapping

SerpApiSerpHubAPI
search_metadata.idmeta.id
search_metadata.statusmeta.status
search_parametersparams
organic_resultsresults.organic
news_resultsresults.news
images_resultsresults.images (original → image)
video_resultsresults.videos
shopping_resultsresults.shopping (source → merchant)
local_resultsresults.places (title → name)
related_questionsresults.people_also_ask
related_searches[].queryresults.related_searches[] (plain strings)
knowledge_graphresults.knowledge_graph
error (string)error.code + error.message

Structural differences

SerpApiSerpHubAPI
RequestGET with query stringPOST with JSON body
Authapi_key query parameterX-API-Key or Authorization: Bearer header
Engine selectionOne engine value per engine and verticalengine + type; engine=auto lets us choose and fail over
Response shapeDiffers per engineOne unified structure for every engine
Cost per callNot in the responsemeta.credits_charged on every response
BillingMonthly subscriptionPrepaid credits; paid balance never expires; empty results free
Page contentNot part of the search callinclude_content on search, or /v1/fetch
Output formatsJSON or HTMLJSON or Markdown (format=markdown)
Errorserror string{"error": {"code", "message"}} with stable codes

Checklist

  1. Create a key at /dashboard/keys.
  2. Switch to POST + JSON + X-API-Key.
  3. Replace engine=google_* / tbm with type.
  4. Replace start with page.
  5. Rename response fields (organic_results → results.organic, etc.).
  6. Handle errors by error.code — see Errors.
  7. Compare a few results side by side, then flip traffic.