Metadata-Version: 2.4
Name: reserp
Version: 0.3.0
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
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 response shapes:

- `urls()` / `search()` calls [`POST /v2/serp/urls`](https://reserp.ai/docs/urls) for a flat, page-ordered, deduplicated URL-and-text index.
- `structured()` calls [`POST /v2/serp/structured`](https://reserp.ai/docs/structured) for typed result families, SERP features, and explicit positions.

[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 describe both public v2 contracts without changing them at runtime.

## Installation

```bash
pip install reserp
```

Python 3.10 or later is required.

## URL index

```python
import os

from reserp import Reserp

with Reserp(api_key=os.environ["RESERP_API_KEY"]) as reserp:
    response = reserp.urls(
        {"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["urls"]:
            print(item.get("text"), item["url"])
    else:
        print(response.status_code, data["error"], data["retryable"], data["billed"])
```

`search()` is an alias for `urls()` and also uses the v2 URL-index 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 result in data["results"]["organic"]:
            print(result["position"], result["title"], result["url"])
        for feature in data["features"]:
            print(feature["page_position"], feature["type"])
```

## 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.urls(
            {"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.urls(
        {"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/urls \
  --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["urls"])` or a structured result-family length.

Error bodies expose `error`, `retryable`, `billed`, and `billing_source`. If your application retries, use `retryable` as the authority and honor `Retry-After` on HTTP 429. The SDK never retries automatically.

## Migrating from v1

For URL-and-text workflows, replace the v1 recursive `results[]` tree with v2 `urls[]`. Other notable renames are `url` → `request.url`, `finalUrl` → `page.url`, `pagination.nextUrl` → `pagination.next_url`, and `billingSource` → `billing_source`. Ranking and SERP-analysis workflows should use `structured()` and the appropriate typed result family.

## License

MIT
