pip install openserpCloud:
importosfromopenserpimportOpenSERPclient=OpenSERP(api_key=os.environ["OPENSERP_API_KEY"])
resp=client.search(engine="google", text="openserp")
print(resp.results[0].title, resp.results[0].url)Self-hosted:
fromopenserpimportOpenSERPclient=OpenSERP(base_url="http://localhost:7000")
resp=client.search(engine="bing", text="openserp")
print(resp.results[0].title, resp.results[0].url)Python SDK for the OpenSERP multi-engine SERP API - Google, Bing, Yandex, Baidu, DuckDuckGo, and Ecosia results in a single call. Works against the self-hosted open-source server and against OpenSERP Cloud with the same code.
Use it for AI grounding, RAG pipelines, LLM tool use, agent tool use, LangChain / LlamaIndex integrations, SEO rank tracking, competitor analysis, and search-powered automations. Open-source alternative to SerpAPI, DataForSEO, ScrapingBee, Bright Data SERP, Oxylabs SERP, and Zenserp.
Also available for TypeScript / JavaScript:
@openserp/sdk.
Alpha - the API may change before
1.0.0. Pin a version in production.
- Install
- Why OpenSERP
- Quickstart - OSS (self-hosted)
- Quickstart - Cloud
- Why two backends?
- Search
- Extract
- Images
- Async
- Endpoint availability
- Telemetry
- Error handling
- Retry hook
- Use cases
pip install openserpDataFrame export is an optional extra:
pip install "openserp[pandas]"Requires Python 3.10+.
| Need | OpenSERP fit |
|---|---|
| Local development | Run the OSS server and use the same SDK surface as Cloud. |
| AI grounding | Pull fresh SERP snippets and optional extracted page text for prompts, RAG, or agents. |
| SEO checks | Query Google, Bing, Yandex, Baidu, DuckDuckGo, and Ecosia with typed models. |
| Migration path | Start self-hosted, then switch to Cloud by adding OPENSERP_API_KEY. |
Compared with hosted-only SERP APIs, OpenSERP keeps the client contract portable. You can test locally without an API key, then use the hosted API when you want managed infrastructure.
Run the open-source server locally, no API key required:
docker run -p 7000:7000 karust/openserp servefromopenserpimportOpenSERPclient=OpenSERP(base_url="http://localhost:7000")
resp=client.search(
engine="google",
text="openserp",
limit=10,
region="US",
)
print(resp.results[0].title, resp.results[0].url)If you pass no options, the client defaults to http://localhost:7000.
Get an API key from the API keys section in the dashboard. When api_key is set, the SDK defaults base_url to https://api.openserp.org/v1 and sends Authorization: Bearer ... for you.
importosfromopenserpimportOpenSERPclient=OpenSERP(api_key=os.environ["OPENSERP_API_KEY"])
resp=client.search(engine="google", text="openserp")
print(resp.results[0].title)
print(client.last_response.credits) # CreditInfo(used=..., remaining=...)If both base_url and api_key are set, base_url wins and the key is still sent. Use this for an authenticated self-hosted deployment. Add backend="oss" when you also need OSS-only methods such as stats() or health().
OpenSERP Cloud uses the same public HTTP contract as the OSS server, with a /v1/ prefix and bearer auth. The same SDK call works on both; you only change base_url / api_key. Start with OSS locally, then move to Cloud when you want the hosted API. See openserp.org/docs/oss-vs-cloud for the full comparison.
single=client.search(engine="bing", text="golang", limit=10, region="US")
mega=client.mega_search(
text="golang",
engines=["google", "bing", "yandex"],
mode="balanced",
limit=20,
)
fast=client.fast_search(text="golang", engines=["google", "bing"])
any_=client.any_search(text="golang", engines=["google", "yandex"])mega_search aggregates multiple engines. mode is "balanced" (default, merged and deduplicated), "any" (first successful engine wins), or "fast" (engines reordered by recent health). fast_search / any_search are sugar for the matching mode.
To enrich top search results with cleaned page content, pass the extraction flags:
grounded=client.search(
engine="google",
text="openserp docs",
extract=3,
extract_mode="auto",
min_runes=500,
)
print(grounded.results[0].extracted.content)page=client.extract(
url="https://openserp.org/docs",
mode="auto",
clean=True,
)
print(page.markdown)Use min_runes to set the auto-mode escalation floor, clean=False for whole-page readable extraction, and use_llms_txt=True to prefer /llms-full.txt or /llms.txt for site-root URLs. Non-JSON formats are returned as strings:
markdown=client.extract(url="https://openserp.org", format="markdown")batch_extract takes up to 20 URLs in one request. A URL that fails becomes an item with an error instead of failing the whole call, so one dead link never costs you the other results:
batch=client.batch_extract(
urls=[
"https://openserp.org/docs",
"https://openserp.org/blog",
],
mode="auto",
)
foriteminbatch.results:
ifitem.error:
print(item.url, "failed:", item.error)
else:
print(item.url, item.page_content[:120])On the hosted API, billing is per URL and matches calling extract that many times - successful extractions bill their mode, failed and empty ones are free.
Pass region (a two-letter country code) to extract as a visitor from that country - useful for geo-fenced or localized pages. On the hosted API this adds 1 credit per successfully extracted URL:
page=client.extract(url="https://example.com/pricing", region="DE")images=client.image(engine="bing", text="golang logo", limit=20)
mega_images=client.mega_image(text="golang logo", engines=["bing", "google"])importasyncio, osfromopenserpimportAsyncOpenSERPasyncdefmain() ->None:
asyncwithAsyncOpenSERP(api_key=os.environ["OPENSERP_API_KEY"]) asclient:
resp=awaitclient.search(engine="google", text="openserp")
print(resp.results[0].title)
asyncio.run(main())Run hundreds of queries concurrently with a semaphore:
importasynciofromopenserpimportAsyncOpenSERPasyncdefmain() ->None:
sem=asyncio.Semaphore(20)
queries= [f"keyword {i}"foriinrange(500)]
asyncwithAsyncOpenSERP() asclient:
asyncdefrun(query: str):
asyncwithsem:
returnawaitclient.search(engine="google", text=query, limit=10)
responses=awaitasyncio.gather(*(run(q) forqinqueries))
print(len(responses))
asyncio.run(main())OSS-only operational methods raise OssOnlyError when the client is configured for Cloud:
client.parse_google(html="<html>...</html>")
client.stats()
client.health()Cloud-only account methods raise CloudOnlyError when the client is configured for OSS:
client.me()
client.pricing()
client.engines_status()
client.engines_capabilities()The backend is inferred from base_url and api_key. Pass backend="oss" or backend="cloud" to the constructor to override.
client.last_response is updated after every HTTP response:
client.last_response.credits# Cloud - CreditInfo(used, remaining)client.last_response.engine_used# both - X-Engine-Usedclient.last_response.fallback_engine# OSS onlyclient.last_response.cache# OSS onlyclient.last_response.headers# raw response headers (lower-cased)Some self-hosted operational headers are not part of the Cloud response contract, so expect those fields to be None against api.openserp.org. credits is Cloud-specific.
fromopenserpimportOpenSERP, RateLimitError, CaptchaError, SERPErrorclient=OpenSERP(api_key="...")
try:
client.search(engine="google", text="openserp")
exceptRateLimitError:
# slow down or queue the request
...
exceptCaptchaError:
# inspect the upstream search failure and retry later
...
exceptSERPErroraserr:
print(err.status, err.code, err.reason, err.request_id)The SDK does not apply a retry policy. Provide a hook when you want one:
importos, random, timefromopenserpimportOpenSERP, SERPErrorRETRYABLE= {408, 429, 500, 502, 503}
client: OpenSERPdefshould_retry(err: Exception, attempt: int) ->bool:
ifattempt>=3ornotisinstance(err, SERPError) orerr.statusnotinRETRYABLE:
returnFalseheaders=client.last_response.headersifclient.last_responseelse {}
retry_after=float(headers.get("retry-after", 0) or0)
wait=retry_afterormin(2**attempt*0.25, 8.0)
time.sleep(wait+random.random() *0.25)
returnTrueclient=OpenSERP(api_key=os.environ["OPENSERP_API_KEY"], retry=should_retry)
client.search(engine="google", text="openserp")- AI grounding / RAG - feed top-N results into an LLM prompt (OpenAI, Anthropic, Ollama) for up-to-date answers.
- LLM tool use - expose
client.searchas a tool to your agent. - SEO monitoring - daily rank tracking across multiple engines and regions, export to a DataFrame or Sheets.
- Competitor analysis - weekly diff of top-10 results for a keyword set.
- Data pipelines - stream SERPs to ClickHouse, BigQuery, or a DataFrame for NLP on snippets.
Quick SEO rank report with pandas:
importpandasaspdfromopenserpimportOpenSERPclient=OpenSERP()
keywords= ["openserp", "serp api", "google search api"]
frames= []
forkeywordinkeywords:
resp=client.search(engine="google", text=keyword, region="US", limit=10)
frame=resp.to_pandas()
frame["keyword"] =keywordframes.append(frame)
pd.concat(frames, ignore_index=True).to_csv("rank-report.csv", index=False)