Metadata-Version: 2.4
Name: reserp
Version: 0.4.1
Summary: Official minimal Python SDK for the Reserp Google Search API v2
Project-URL: Homepage, https://reserp.ai
Project-URL: Documentation, https://reserp.ai/docs
Project-URL: Repository, https://github.com/reserp-ai/reserp-python
Project-URL: Issues, https://github.com/reserp-ai/reserp-python/issues
Project-URL: Changelog, https://github.com/reserp-ai/reserp-python/blob/main/CHANGELOG.md
Author-email: Reserp <no-reply@reserp.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: api-client,google-search,google-search-api,google-serp-api,python,reserp,sdk,search-api,search-results-api,serp,serp-api,serp-data
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: typing-extensions>=4.4
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == 'dev'
Requires-Dist: mypy<2,>=1.13; extra == 'dev'
Requires-Dist: pytest<10,>=8.3; extra == 'dev'
Requires-Dist: ruff<1,>=0.8; extra == 'dev'
Requires-Dist: twine<7,>=5.1; extra == 'dev'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://reserp.ai">
    <img src="https://reserp.ai/icon-512.png" alt="Reserp Google Search API" width="112" height="112">
  </a>
</p>

# Reserp Python SDK

[![PyPI version](https://img.shields.io/pypi/v/reserp.svg)](https://pypi.org/project/reserp/)
[![Python versions](https://img.shields.io/pypi/pyversions/reserp.svg)](https://pypi.org/project/reserp/)
[![CI](https://github.com/reserp-ai/reserp-python/actions/workflows/ci.yml/badge.svg)](https://github.com/reserp-ai/reserp-python/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

The official minimal Python client for [Reserp v2](https://reserp.ai/docs), a Google Search API with two stable response shapes:

- `search()` calls [`POST /v2/serp/search`](https://reserp.ai/docs/search) for flat, page-ordered, deduplicated results in `results[]`.
- `structured()` calls [`POST /v2/serp/structured`](https://reserp.ai/docs/structured) for best-effort extraction of typed, page-ordered SERP blocks in `blocks[]`.

[Website](https://reserp.ai) · [API documentation](https://reserp.ai/docs) · [OpenAPI 3.1](https://reserp.ai/openapi.json) · [Postman](https://www.postman.com/reserp-ai/reserp-google-search-api) · [Pricing](https://reserp.ai/pricing)

## Design

Each SDK call makes exactly one API request and returns the native `httpx.Response` unchanged. The package adds no retry, timeout, URL-building, validation, pagination, transformation, cache, batch, queue, or concurrency policy. Typed dictionaries generated from the canonical schema describe both v2 contracts without changing them at runtime.

## Installation

```bash
pip install reserp
```

Python 3.10 or later is required.

## Search results

```python
import os

from reserp import Reserp

with Reserp(api_key=os.environ["RESERP_API_KEY"]) as reserp:
    response = reserp.search(
        {"url": "https://www.google.com/search?q=best+pizza+in+dubai&gl=ae&hl=en"}
    )
    data = response.json()

    if data["ok"]:
        for item in data["results"]:
            print(item.get("text"), item["url"])
    else:
        print(response.status_code, data["error"], data["retryable"], data["billed"])
```

The deprecated `urls()` method is a compatibility alias for `search()` and uses the stable Search endpoint.

## Structured results

```python
with Reserp(api_key=os.environ["RESERP_API_KEY"]) as reserp:
    response = reserp.structured(
        {"url": "https://www.google.com/search?q=wireless+earbuds&gl=us&hl=en&tbm=shop"}
    )
    data = response.json()

    if data["ok"]:
        for block in data["blocks"]:
            print(block["position"], block["type"], block["title"])
            if block["type"] == "organic":
                for item in block["items"]:
                    print(item["position"], item["title"], item["url"])
```

Detailed block and item types are available from `reserp.types`.

## Async client

```python
import asyncio
import os

from reserp import AsyncReserp


async def main() -> None:
    async with AsyncReserp(api_key=os.environ["RESERP_API_KEY"]) as reserp:
        response = await reserp.search(
            {"url": "https://www.google.com/search?q=photonic+computing&gl=us&hl=en"}
        )
        print(response.status_code, response.json())


asyncio.run(main())
```

## Native transport control

Inject an HTTPX client for transport policy and pass request options directly to the matching client method:

```python
import httpx

limits = httpx.Limits(max_connections=50, max_keepalive_connections=20)
timeout = httpx.Timeout(20.0)

with httpx.Client(limits=limits, timeout=timeout) as transport:
    reserp = Reserp(api_key=os.environ["RESERP_API_KEY"], client=transport)
    response = reserp.search(
        {"url": "https://www.google.com/search?q=semiconductors&gl=us&hl=en&tbs=qdr:w"},
        headers={"x-request-id": "your-job-id"},
        follow_redirects=False,
    )
```

Transport failures remain native HTTPX exceptions. HTTP error responses remain native responses; inspect their status, headers, and JSON body.

## Direct HTTP equivalents

```bash
curl https://api.reserp.ai/v2/serp/search \
  --request POST \
  --header "Authorization: Bearer $RESERP_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"url":"https://www.google.com/search?q=photonic+computing&gl=us&hl=en"}'

curl https://api.reserp.ai/v2/serp/structured \
  --request POST \
  --header "Authorization: Bearer $RESERP_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"url":"https://www.google.com/search?q=photonic+computing&gl=us&hl=en"}'
```

## Pagination and errors

Every successful response contains `pagination.next_url`. Send that URL back as the next request body's `url`; its presence does not guarantee that another page contains results. Do not calculate pagination from `len(data["results"])`, `len(data["blocks"])`, or any block's item count.

Error bodies expose `error`, `message`, `doc_url`, `retryable`, `billed`, and `billing_source`. Use `message` and `doc_url` for diagnostics; message wording may change, so branch on the stable `error` code and `retryable` flag. If your application retries, use `retryable` as the authority and honor `Retry-After` on HTTP 429. The SDK never retries automatically.

## Migrating

From SDK 0.3, replace `urls()` with `search()`, `/v2/serp/urls` with `/v2/serp/search`, and `data["urls"]` with `data["results"]`. `urls()` remains as a deprecated method alias, but its response now follows the stable Search contract.

If you used the structured beta, replace `schema_version`, grouped `results`, `features`, `page_position`, and `metadata` with the stable, page-ordered `blocks[]` model. Each block has `type` and `position`; block-specific entries live in `items[]`.

When migrating directly from v1, other notable renames are `url` → `request.url`, `finalUrl` → `page.url`, `pagination.nextUrl` → `pagination.next_url`, and `billingSource` → `billing_source`.

## License

MIT
