SerpHubAPI

Migrate from Serper.dev

Map Serper.dev requests and responses to SerpHubAPI — same POST + JSON style, plus Bing and Yandex.

If you use Serper.dev, the move is short: both APIs take a POST with a JSON body and the same core parameters. The main changes are the URL, the header name, and choosing the vertical with type instead of a separate path.

Quick switch

Before (Serper.dev):

curl -X POST https://google.serper.dev/search \
  -H "X-API-KEY: $SERPER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q": "coffee", "gl": "us", "hl": "en", "num": 10, "page": 2, "tbs": "qdr:w"}'

After (SerpHubAPI):

curl -X POST https://api.serphubapi.com/v1/search \
  -H "X-API-Key: $SERPHUBAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q": "coffee", "engine": "google", "gl": "us", "hl": "en", "num": 10, "page": 2, "time_range": "week"}'

In Python:

import os, httpx

# before: httpx.post("https://google.serper.dev/search", headers={"X-API-KEY": key}, json=body)
r = httpx.post(
    "https://api.serphubapi.com/v1/search",
    headers={"X-API-Key": os.environ["SERPHUBAPI_API_KEY"]},
    json={"q": "coffee", "engine": "google", "gl": "us", "hl": "en", "num": 10},
)
organic = r.json()["results"].get("organic", [])

Endpoint mapping

Serper.devSerpHubAPI
POST google.serper.dev/searchPOST /v1/search with type=web
POST google.serper.dev/newstype=news
POST google.serper.dev/imagestype=images
POST google.serper.dev/videostype=videos
POST google.serper.dev/shoppingtype=shopping
POST google.serper.dev/maps / /placestype=maps
POST google.serper.dev/scholartype=scholar
POST google.serper.dev/autocompletetype=autocomplete
POST scrape.serper.devPOST /v1/fetch, or include_content on search
—engine=bing / engine=yandex (web)

All on base URL https://api.serphubapi.com.

Parameter mapping

Serper.devSerpHubAPINotes
X-API-KEY headerX-API-Key header (or Authorization: Bearer)Header names are case-insensitive, so only the value changes
qqSame
glglSame
hlhlSame
numnum1–100, same price for any value
pagepageSame, 1-based
tbs=qdr:dtime_range=day
tbs=qdr:wtime_range=week
tbs=qdr:mtime_range=month
tbs=qdr:ytime_range=year
tbs=qdr:h, custom date ranges—Not supported
location—Not supported; use gl / hl
autocorrect—Not supported
—engineauto (default), google, bing, yandex
—modelive (default) or cache_first (half price, free on cache hit)
—include_contentFetch top N pages as Markdown in the same call
—formatjson or markdown

Response mapping

Serper.devSerpHubAPI
searchParametersparams
creditsmeta.credits_charged (10 = one live search)
organicresults.organic
newsresults.news
images[].imageUrl / thumbnailUrlresults.images[].image / thumbnail
videosresults.videos
shopping[].sourceresults.shopping[].merchant
places[].title / ratingCountresults.places[].name / reviews
peopleAlsoAskresults.people_also_ask
relatedSearches[].queryresults.related_searches[] (plain strings)
knowledgeGraphresults.knowledge_graph
suggestions[].valueresults.suggestions[] (plain strings)
—meta.id — call ID, look up again for 30 days via GET /v1/searches/{id}

Field names are snake_case throughout.

Structural differences

Serper.devSerpHubAPI
EnginesGoogleGoogle, Bing, Yandex (engine=auto chooses and fails over)
Choosing a verticalSeparate path per verticalOne endpoint, type parameter
Response shapePer-vertical keys, camelCaseOne unified structure, snake_case
Call ID and archive—meta.id, 30-day archive at /v1/searches/{id}
Page contentSeparate scrape endpointinclude_content in the search call, or /v1/fetch
LLM-ready outputJSONJSON or Markdown (format=markdown)
BillingPrepaid credit packsPrepaid credits; paid balance never expires; failed calls, empty results and cache hits are free
ErrorsHTTP status + message{"error": {"code", "message"}} with stable codes — see Errors

Checklist

  1. Create a key at /dashboard/keys.
  2. Change the base URL to https://api.serphubapi.com/v1/search and set type instead of the path.
  3. Translate tbs to time_range.
  4. Rename camelCase response fields to their snake_case equivalents under results.
  5. Read cost from meta.credits_charged instead of credits.
  6. Run a few queries side by side, then switch traffic.