Skip to main content

serpex

Official Python SDK for Serpex.

Serpex is a web search API and extract API for AI agents. Search returns ranked web results, optionally with page content as markdown; Extract turns known URLs into clean markdown. Serpex runs its own search engine.

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 dashboard
  • base_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 are deprecated too: they are always "auto" today, are optional in the SDK (defaults ["auto"] / None), and may be removed from responses in a later API version, so don't build logic on them.

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
    # Present only when status == "no_results"
    no_results_verified: Optional[bool] = None
    charged: Optional[bool] = None
    message: Optional[str] = None

@dataclass
class SearchResult:
    title: str
    url: str
    snippet: str
    position: int
    engine: Optional[str] = None  # deprecated, always "auto"
    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] = ["auto"]  # deprecated
    results: List[SearchResult] = []
    message: Optional[str] = None  # present only when no results were found

Usage & credit balance

Check your credit balance and request history — useful before a large batch. Statistics and credits cover your whole organization (every API key in it); usage.api_key is the NAME of the key that made the call.

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.noResultsRequests)  # zero-result searches in the period
print(usage.statistics.engineStats)    # {"search": 120, "crawl": 30, "stealth": 5}

Timeouts

Each call waits longer than the server's own budget for it, so the SDK never gives up on a request the server still finishes and bills: 60 s search, 100 s search with include_content, 100 s extract, 120 s stealth extract. Override every call with SerpexClient(api_key, timeout=45).

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

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.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for serpex 2.11.0
File Size Uploaded
serpex-2.11.0.tar.gz 14.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for serpex 2.11.0
File Interpreter ABI Platform
serpex-2.11.0-py3-none-any.whl Python 3 none any Details

Total release size: 28.8 kB

Release files / serpex-2.11.0.tar.gz

Download URL serpex-2.11.0.tar.gz
Size 14.7 kB
Tags Source
SHA-256 checksum
How to use checksums
98d6b902f683dad6eb79d5fd6f67fd3360c5c31bed5e0c270910786d88256f44
BLAKE2b-256 checksum
How to use checksums
be602ccea356c7c93704ad4ddfdb74d39e603b76fca11c931c937f8ae521922b
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.11.0-py3-none-any.whl

Download URL serpex-2.11.0-py3-none-any.whl
Size 14.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
62df482ae5831ea50d0e1116045da79ad077afe592d5d387eb6efc6f3ec17e47
BLAKE2b-256 checksum
How to use checksums
77514de7ab4aa8c7676cf3aa0920061a028d1de2a8b6dba6e870c46aa87783c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

2.11.0 This release

2 release files

2.10.3

2 release files

2.10.2

2 release files

2.10.1

2 release files

2.10.0

2 release files

2.9.0

2 release files

2.8.1

2 release files

2.8.0

2 release files

2.7.0

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page