serpex
Official Python SDK for Serpex — a real-time web search API, plus page content
extraction (extract) that turns any URL into LLM-ready markdown. Built for AI
agents, LLM tools and RAG pipelines.
Installation
pip install serpex
Or with poetry:
poetry add serpex
Quick Start
from serpex import SerpexClient
# Initialize the client with your API key
client = SerpexClient('your-api-key-here')
# One search engine — no engine to pick
results = client.search({'q': 'python tutorial'})
# Or using SearchParams object for type safety
from serpex import SearchParams
params = SearchParams(q='python tutorial')
results = client.search(params)
print(results.results[0].title)
API Reference
SerpexClient
Constructor
SerpexClient(api_key: str, base_url: str = "https://api.serpex.dev")
api_key: Your API key from the Serpex dashboardbase_url: Optional base URL (defaults to 'https://api.serpex.dev')
Methods
extract(params: ExtractParams | Dict[str, Any]) -> ExtractResponse
Extract content from web pages and convert them to LLM-ready markdown data. Accepts up to 10 URLs per request.
# Basic usage
results = client.extract({
'urls': [
'https://example.com',
'https://httpbin.org'
]
})
# With stealth mode and HTML output
results = client.extract({
'urls': ['https://example.com'],
'stealth': True,
'format': 'html'
})
# Using ExtractParams object (type-safe approach)
from serpex import ExtractParams
params = ExtractParams(
urls=['https://example.com'],
stealth=True,
format='html'
)
results = client.extract(params)
Extract Parameters
The ExtractParams dataclass supports extraction parameters:
@dataclass
class ExtractParams:
# Required: URLs to extract (max 10)
urls: List[str]
# Optional: premium extraction mode for pages that standard extraction can't read (default: False)
stealth: bool = False
# Optional: Output format — 'markdown' (default) or 'html'
format: str = 'markdown'
Extract Response Format
@dataclass
class ExtractResponse:
success: bool
results: List[ExtractResult]
metadata: ExtractMetadata
@dataclass
class ExtractResult:
url: str
success: bool
markdown: Optional[str] = None
html: Optional[str] = None # Populated when format='html'
stealth: Optional[bool] = None # Whether stealth mode was used for this result
error: Optional[str] = None
status_code: Optional[int] = None
@dataclass
class ExtractMetadata:
total_urls: int
processed_urls: int
successful_crawls: int
failed_crawls: int
credits_used: int
response_time: int
timestamp: str
cached_free: Optional[int] = None # URLs served from cache (no credit charge)
Search Parameters
The SearchParams dataclass supports all search parameters:
@dataclass
class SearchParams:
# Required: search query
q: str
# Optional: also fetch page content (markdown) for top results (default: False)
include_content: bool = False
# Optional: number of top results to fetch content for — must be exactly
# 5 or 10 (default: 5). Only relevant when include_content is True.
content_results: Literal[5, 10] = 5
| Param | Type | Default | Notes |
|---|---|---|---|
q |
str |
— | Required search query (max 500 chars) |
include_content |
bool |
False |
Also fetch page content (markdown) for top results |
content_results |
Literal[5, 10] |
5 |
How many top results to fetch content for; must be exactly 5 or 10 |
The engine parameter (deprecated)
Serpex is one search engine, so there is nothing to select. The legacy
engine / engines request parameters are deprecated and ignored by the API
(since 2026-06); requests that still send them get a Deprecation response
header. SearchParams(engine=...) and client.search({'q': ..., 'engine': ...})
are still accepted so existing code keeps working — the SDK emits a
DeprecationWarning and does not send the value. The engines / engine
response fields remain for compatibility.
Response Format
@dataclass
class SearchMetadata:
number_of_results: int
response_time: int
timestamp: str
credits_used: int
from_cache: Optional[bool] = None
status: Optional[str] = None
# Present only when include_content was requested
content_requested: Optional[int] = None
content_delivered: Optional[int] = None
@dataclass
class SearchResult:
title: str
url: str
snippet: str
position: int
engine: str
img_src: Optional[str] = None
duration: Optional[str] = None
score: Optional[float] = None
# Present only when include_content was requested. Best-effort — a
# failed extraction sets content_error instead of content.
content: Optional[str] = None
content_error: Optional[str] = None
@dataclass
class SearchResponse:
metadata: SearchMetadata
id: str
query: str
engines: List[str]
results: List[SearchResult]
Usage & credit balance
Check your credit balance and request history — useful before a large batch.
usage = client.usage() # last 30 days
week = client.usage({"days": 7}) # 1-90 (larger values are capped at 90)
print(usage.credits.balance) # credits remaining
print(usage.statistics.totalRequests) # requests in the period
print(usage.statistics.engineStats) # {"search": 120, "crawl": 30, "stealth": 5}
Stealth error codes
When stealth=True, a failed result carries a stable error_code alongside the
human-readable error. Branch on the code rather than parsing the message — it
tells you whether the problem is with your URL or with our service:
error_code |
error_type |
Meaning | Retry? |
|---|---|---|---|
stealth_target_unreachable |
connection |
The domain did not resolve or refused the connection — the site is likely gone | No |
stealth_target_status |
http |
The page answered with an error status (see status_code) |
No |
stealth_target_empty |
blocked |
The page answered 200 with no usable content |
Maybe |
stealth_timeout |
timeout |
The page did not finish rendering in time | Yes |
stealth_provider_unavailable |
server_error |
Our stealth extraction service was unavailable — not a problem with your URL | Yes |
stealth_network |
connection |
Network error inside our stealth extraction service | Yes |
stealth_unconfigured |
server_error |
Stealth is not enabled on this deployment | No |
response = client.extract({"urls": urls, "stealth": True})
for r in response.results:
if r.success:
continue
if r.error_code == "stealth_target_unreachable":
pass # The domain is dead — drop it from your list.
elif r.error_code == "stealth_provider_unavailable":
pass # Our side. Safe to retry shortly.
Error Handling
The SDK raises SerpApiException for API errors:
from serpex import SerpexClient, SerpApiException
try:
results = client.search(SearchParams(q='test query'))
except SerpApiException as e:
print(f"API error: {e}")
print(f"Status code: {e.status_code}")
print(f"Details: {e.details}")
Examples
Basic Search
results = client.search({
'q': 'coffee shops near me'
})
Search with Page Content
Fetch page content (markdown) for the top results inline with the search —
best-effort, so check each result for content vs content_error.
results = client.search({
'q': 'best espresso machines 2025',
'include_content': True,
'content_results': 10, # must be exactly 5 or 10
})
print(
f"Content delivered for {results.metadata.content_delivered}/"
f"{results.metadata.content_requested} requested results"
)
for result in results.results:
if result.content:
print(f"✅ {result.url}: {len(result.content)} chars of markdown")
elif result.content_error:
print(f"❌ {result.url}: {result.content_error}")
Extract Web Content to LLM-Ready Data
Extract from a Single URL
# Extract content from one website (markdown, default)
result = client.extract({
'urls': ['https://example.com']
})
if result.results[0].success:
print(f"✅ Extracted {len(result.results[0].markdown)} characters")
print("Markdown content:", result.results[0].markdown[:200] + "...")
# Extract with stealth mode and HTML output
stealth_result = client.extract({
'urls': ['https://example.com'],
'stealth': True,
'format': 'html'
})
if stealth_result.results[0].success:
print("HTML content:", stealth_result.results[0].html[:200])
Extract from Multiple URLs (up to 10 at once)
# Extract content from multiple websites (up to 10 URLs)
extract_results = client.extract({
'urls': [
'https://example.com',
'https://httpbin.org',
'https://github.com'
]
})
print(f"Successfully extracted {extract_results.metadata.successful_crawls} pages")
print(f"Total credits used: {extract_results.metadata.credits_used}")
for result in extract_results.results:
if result.success:
print(f"✅ {result.url}: {len(result.markdown)} characters")
# Use result.markdown for LLM processing
else:
print(f"❌ {result.url}: {result.error}")
Sample Response
# Example response structure
{
'success': True,
'results': [
{
'url': 'https://example.com',
'success': True,
'markdown': '# Example Domain\n\nThis domain is for use in...',
'stealth': False,
'status_code': 200
}
],
'metadata': {
'total_urls': 1,
'processed_urls': 1,
'successful_crawls': 1,
'failed_crawls': 0,
'credits_used': 3,
'cached_free': 0,
'response_time': 255,
'timestamp': '2025-11-13T10:30:00.000Z'
}
}
Using ExtractParams Object
from serpex import ExtractParams
params = ExtractParams(urls=[
'https://example.com',
'https://httpbin.org'
])
results = client.extract(params)
Requirements
- Python 3.8+
- requests
License
MIT
Metadata
Release files for serpex 2.10.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| serpex-2.10.3.tar.gz | 13.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| serpex-2.10.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.9 kB
Release files / serpex-2.10.3.tar.gz
| Download URL | serpex-2.10.3.tar.gz |
|---|---|
| Size | 13.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2edb18f5425f7d10f973383f715dff6b8b9cbd64174f881260c0237fd644e2bf
|
|
BLAKE2b-256 checksum How to use checksums |
8d49e9d23559e91a436ba88692cfca09e824996a615c37a9f5f55b3e236c22ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / serpex-2.10.3-py3-none-any.whl
| Download URL | serpex-2.10.3-py3-none-any.whl |
|---|---|
| Size | 12.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d43eeac5251504fb4939a645a8a1318580982573285389be86fa3b481061c74f
|
|
BLAKE2b-256 checksum How to use checksums |
ac0af08267c7910878025572fab07da32899f49318158525d7c3da7c1d84902a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|