Python SDK for the OpenSERP self-hosted server and OpenSERP Cloud.
Project description
openserp
pip install openserp
Cloud:
import os
from openserp import OpenSERP
client = OpenSERP(api_key=os.environ["OPENSERP_KEY"])
resp = client.search(engine="google", text="openserp")
print(resp.results[0].title, resp.results[0].url)
Self-hosted:
from openserp import OpenSERP
client = 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(source).
Alpha — the API may change before
1.0.0. Pin a version in production.
Contents
- Install
- Quickstart — OSS (self-hosted)
- Quickstart — Cloud
- Why two backends?
- Search
- Images
- Async
- Endpoint availability
- Telemetry
- Error handling
- Retry hook
- Use cases
- Development
Install
pip install openserp
DataFrame export is an optional extra:
pip install "openserp[pandas]"
Requires Python 3.10+.
Quickstart — OSS (self-hosted)
Run the open-source server locally, no API key required:
docker run -p 7000:7000 karust/openserp serve
from openserp import OpenSERP
client = 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.
Quickstart — Cloud
Get an API key at the dashboard. When api_key is set, the SDK defaults base_url to https://api.openserp.org/v1 and sends Authorization: Bearer ... for you.
import os
from openserp import OpenSERP
client = OpenSERP(api_key=os.environ["OPENSERP_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.
Why two backends?
OpenSERP Cloud is the same 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 for free, move to Cloud when you need managed proxies, captcha handling, and pre-warmed routing without the operational work. See openserp.org/docs/oss-vs-cloud for the full comparison.
Search
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.
Images
images = client.image(engine="bing", text="golang logo", limit=20)
mega_images = client.mega_image(text="golang logo", engines=["bing", "google"])
Async
import asyncio, os
from openserp import AsyncOpenSERP
async def main() -> None:
async with AsyncOpenSERP(api_key=os.environ["OPENSERP_KEY"]) as client:
resp = await client.search(engine="google", text="openserp")
print(resp.results[0].title)
asyncio.run(main())
Run hundreds of queries concurrently with a semaphore:
import asyncio
from openserp import AsyncOpenSERP
async def main() -> None:
sem = asyncio.Semaphore(20)
queries = [f"keyword {i}" for i in range(500)]
async with AsyncOpenSERP() as client:
async def run(query: str):
async with sem:
return await client.search(engine="google", text=query, limit=10)
responses = await asyncio.gather(*(run(q) for q in queries))
print(len(responses))
asyncio.run(main())
Endpoint availability
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.
Telemetry
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-Used
client.last_response.fallback_engine # OSS only
client.last_response.cache # OSS only
client.last_response.headers # raw response headers (lower-cased)
fallback_engine and cache are deliberately hidden by Cloud, so expect them to be None against api.openserp.org. credits is the inverse: only present when talking to Cloud.
Error handling
from openserp import OpenSERP, RateLimitError, CaptchaError, SERPError
client = OpenSERP(api_key="...")
try:
client.search(engine="google", text="openserp")
except RateLimitError:
# slow down or queue the request
...
except CaptchaError:
# OSS may surface upstream captcha challenges; on Cloud this is handled for you
...
except SERPError as err:
print(err.status, err.code, err.reason, err.request_id)
Retry hook
The SDK does not apply a retry policy. Provide a hook when you want one:
import os, random, time
from openserp import OpenSERP, SERPError
RETRYABLE = {408, 429, 500, 502, 503}
client: OpenSERP
def should_retry(err: Exception, attempt: int) -> bool:
if attempt >= 3 or not isinstance(err, SERPError) or err.status not in RETRYABLE:
return False
headers = client.last_response.headers if client.last_response else {}
retry_after = float(headers.get("retry-after", 0) or 0)
wait = retry_after or min(2 ** attempt * 0.25, 8.0)
time.sleep(wait + random.random() * 0.25)
return True
client = OpenSERP(api_key=os.environ["OPENSERP_KEY"], retry=should_retry)
client.search(engine="google", text="openserp")
Use cases
- 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:
import pandas as pd
from openserp import OpenSERP
client = OpenSERP()
keywords = ["openserp", "serp api", "google search api"]
frames = []
for keyword in keywords:
resp = client.search(engine="google", text=keyword, region="US", limit=10)
frame = resp.to_pandas()
frame["keyword"] = keyword
frames.append(frame)
pd.concat(frames, ignore_index=True).to_csv("rank-report.csv", index=False)
Development
python -m pip install -e ".[dev,pandas]"
pytest
ruff check .
mypy src
python -m build
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openserp-0.1.2.tar.gz.
File metadata
- Download URL: openserp-0.1.2.tar.gz
- Upload date:
- Size: 12.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6bc163c71a195efddeb3344fdf38a45f833a7d5a3bcab562fe385518de4fff4
|
|
| MD5 |
0a9b628330316de394f0186675c3637c
|
|
| BLAKE2b-256 |
e0a01ae4cd7d9e64538907818172361ec1a84d46835b314031925f93d9ac7cd4
|
Provenance
The following attestation bundles were made for openserp-0.1.2.tar.gz:
Publisher:
sdk-python.yml on karust/openserp_project
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openserp-0.1.2.tar.gz -
Subject digest:
e6bc163c71a195efddeb3344fdf38a45f833a7d5a3bcab562fe385518de4fff4 - Sigstore transparency entry: 1622176555
- Sigstore integration time:
-
Permalink:
karust/openserp_project@628a5f15adcc6dfde9d13e7b6820e926a9c44063 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/karust
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
sdk-python.yml@628a5f15adcc6dfde9d13e7b6820e926a9c44063 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file openserp-0.1.2-py3-none-any.whl.
File metadata
- Download URL: openserp-0.1.2-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebba396b076ae50abf0c34f59e17b7e01f1004807ff394b2a8d312745fa29419
|
|
| MD5 |
77d96a5dc4cd284190932788d6949991
|
|
| BLAKE2b-256 |
084a90fa451eea3b9e6042a89ca1aa4b940bd4f4ddc72f28b9e367953983051d
|
Provenance
The following attestation bundles were made for openserp-0.1.2-py3-none-any.whl:
Publisher:
sdk-python.yml on karust/openserp_project
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openserp-0.1.2-py3-none-any.whl -
Subject digest:
ebba396b076ae50abf0c34f59e17b7e01f1004807ff394b2a8d312745fa29419 - Sigstore transparency entry: 1622176645
- Sigstore integration time:
-
Permalink:
karust/openserp_project@628a5f15adcc6dfde9d13e7b6820e926a9c44063 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/karust
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
sdk-python.yml@628a5f15adcc6dfde9d13e7b6820e926a9c44063 -
Trigger Event:
workflow_dispatch
-
Statement type: