Metadata-Version: 2.4
Name: dataswap
Version: 0.2.0
Summary: Official Python client for the Dataswap API — structured SERP, keyword, competitive and marketplace data, billed in credits.
Project-URL: Homepage, https://dataswap.io
Project-URL: Documentation, https://dataswap.io/reference
Project-URL: Issues, https://dataswap.io/contact
Author: Dataswap
License: MIT
License-File: LICENSE
Keywords: ai-search,backlinks,dataswap,google-search,keywords,rag,rank-tracking,search-api,seo,serp,serp-api
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# Dataswap — Python SDK

Official Python client for the [Dataswap API](https://dataswap.io): Google SERP, Maps, News,
Shopping, Images, Amazon, business profiles, reviews, on-page audits, product catalog extraction,
12 AI tools and the intelligence layer (grounding, schema extraction, AI visibility, entity cards,
commerce matching, signed provenance) — one API key, credit-based pricing.

- **No dependencies.** Standard library only (`urllib`). Python 3.9+.
- **Waits for long jobs.** Slow operations hand off with `202` and a `job_id`; the client polls and
  returns the result. From your side, the call just takes longer.
- **Safe retries.** Every request carries an `Idempotency-Key`, and retries **reuse it** — a retried
  timeout is never billed twice.

```bash
pip install dataswap
```

## Quick start

```python
from dataswap import Dataswap

client = Dataswap()  # reads DATASWAP_API_KEY from the environment

serp = client.search(q="best running shoes", gl="us", hl="en")
print(serp["organic_results"][0]["title"])
print(serp.billing.credits_remaining, "credits left")
```

`search()` returns a `dict` — the exact JSON the API returned — with a `.billing` attribute
attached. It serializes, indexes and iterates like any other dict.

## Authentication

Get a key at [dataswap.io/dashboard/keys](https://dataswap.io/dashboard/keys). Either export it:

```bash
export DATASWAP_API_KEY="sk_live_..."
```

or pass it explicitly:

```python
client = Dataswap(api_key="sk_live_...")
```

Keys are shown once, at creation. Only a hash is stored, so a lost key is rotated, never recovered.

## What you can call

```python
# Search
client.search(q="...", gl="us")        # Google Search   — 1 credit
client.news(q="...")                   # Google News     — 1 credit
client.maps(q="...", location="Lisbon") # Google Maps     — 2 credits
client.images(q="...")                 # Google Images   — 2 credits
client.shopping(q="...")               # Google Shopping — 3 credits

# Marketplaces, businesses, reviews, apps
client.amazon.products({"keyword": "usb-c hub"})
client.amazon.asin({"asin": "B0CHX1W1XY"})
client.business.info({"keyword": "clinica dentaria braga"})
client.reviews.google({"place_id": "..."})
client.apps.search({"keyword": "habit tracker"})

# On-page
client.onpage.instant({"url": "https://example.com"})
client.onpage.summary({"target": "example.com", "max_crawl_pages": 20})

# AI (12 tools)
client.ai.answer({"query": "who won the 2026 world cup?"})
client.ai.content_brief({"keyword": "protein powder",
                         "results": [{"title": "...", "url": "..."}]})
client.ai.rerank({"query": "...", "documents": [...]})

# Intelligence layer (see below)
client.context("eu ai act deadlines", token_budget=2000)
client.entity("example.com")

# Account
client.usage()
client.pricing()
```

Every namespaced method takes the request body as a single dict. Full parameter reference:
[dataswap.io/reference](https://dataswap.io/reference).

## Long-running jobs

Some operations take longer than a request should. Those return `202 Accepted` with a `job_id`
instead of a result. **The SDK handles this for you** — the call blocks until the job finishes:

```python
audit = client.onpage.summary({"target": "example.com", "max_crawl_pages": 50})
print(audit["pages_crawled"])   # already the finished result
```

To handle the handoff yourself, disable the wait:

```python
client = Dataswap(wait_for_job=0)

job = client.onpage.summary({"target": "example.com"})
print(job["job_id"], job["status"])   # 'processing'

# ...later, from anywhere:
done = client.job(job["job_id"])
if done["status"] == "done":
    print(done["result"], done["credits_used"])
```

The same endpoint may answer `200` **or** `202` depending on how long the work takes. Code that only
handles one of the two breaks intermittently — which is exactly why the default is to wait.

### Catalog extraction

```python
catalog = client.extract_catalog("https://shop.example", max_products=500)
print(catalog["products_extracted"], "products for", catalog["credits_charged"], "credits")
```

`max_products` is the ceiling reserved up front, not what you pay: billing is per product actually
extracted, and the difference is refunded.

## Intelligence layer

Eight products that answer a question instead of returning rows. Inference runs on self-hosted
models, and every response carries a signed provenance receipt.

```python
# Grounding for an agent, trimmed to a token budget — reranked and deduplicated.
pack = client.context("eu ai act deadlines", token_budget=2000)
prompt = "\n\n".join(f"[{b['url']}] {b['text']}" for b in pack["blocks"])

# Your JSON Schema, filled from the web, with a citation per field.
filled = client.extract_schema(
    {"type": "object", "properties": {"plan": {"type": "string"}, "price_usd": {"type": "number"}}},
    query="acme corp pricing",
)
[f for f in filled["fields"] if f["source_idx"] is None]   # no evidence → the value is None

# Share of answer across search, AI Overview and LLM answers.
land = client.geo_answer_landscape("best crm for startups")
land["share_of_answer"][0]["domain"]

# Domain 360, every signal captured at the same instant. Expect 10-20 s.
card = client.entity("example.com", include=["backlinks", "tech"])

# Are these offers the same product? same / variant / bundle / different, with evidence.
m = client.commerce_match({"title": "Sony WH-1000XM5", "gtin": "4548736132115"})

# Share of digital shelf, including what the AI Overview cites.
shelf = client.commerce_shelf(["noise cancelling headphones"], brand="Acme")

# Verify any receipt — public, free, no key needed by whoever checks it.
body = {k: v for k, v in card.items() if k != "provenance"}
client.provenance_verify(card["provenance"], payload=body)["valid"]
```

Two things worth knowing before you budget:

- **The price varies with the call.** A ceiling is held while the work runs, the charge is sealed
  against the real cost, and the difference is refunded. `billing.credits_used` is what you paid;
  `billing.credits_reserved` is what was held, when the two differ.
- **They are slow by design** — they compose several operations. `timeout` is per attempt and
  defaults to 60 s; raise it before calling `commerce_match` (up to ~50 s) over a slow link.

Numbers (shares, percentiles, consensus) are computed in code, not by a model, so the same inputs
give the same output. Missing data is reported (`partial`, `unavailable`, `coverage`), never faked.

These read as flat methods (`commerce_match`) rather than namespaces, because each one has its own
signature and its own keyword arguments — the same choice `extract_catalog` already made. The
endpoints are exactly the ones the Node SDK exposes; a test in each package fails if they diverge.

## Errors

Every failure raises `DataswapError`. Branch on `.code` — it's the stable contract; `.message` is
human-readable and may change.

```python
from dataswap import Dataswap, DataswapError

try:
    serp = client.search(q="...")
except DataswapError as e:
    if e.is_insufficient_credits:
        top_up()                       # retrying will not create balance
    elif e.is_rate_limited:
        time.sleep(e.retry_after or 5)
    elif e.is_missing_scope:
        ...                            # the key is valid — do not discard it
    elif e.is_auth_error:
        rotate_key()                   # here the credential *is* the problem
    else:
        log.error("dataswap %s (request %s)", e.code, e.request_id)
```

Include `e.request_id` in any support request — it identifies the exact call in our logs.

`429` and `5xx` are retried automatically (honouring `Retry-After`). `402 insufficient_credits` is
never retried. Validation errors are never retried.

## Configuration

```python
client = Dataswap(
    api_key=None,                        # defaults to $DATASWAP_API_KEY
    base_url="https://api.dataswap.io",
    timeout=60.0,                        # seconds per HTTP request
    max_retries=2,                       # retries for 429/5xx
    wait_for_job=300.0,                  # seconds to wait on a 202; 0 returns the handoff
)
```

## Billing

Each result carries the accounting for that call:

```python
r = client.search(q="...")
r.billing.credits_used        # what this call cost
r.billing.credits_reserved    # ceiling held during the call, when it differed from what was charged
r.billing.credits_remaining   # balance after it
r.billing.request_id          # for support
r.billing.idempotent_replay   # True if this replayed an earlier identical request
r.billing.elapsed_ms
r.billing.job_id              # set when the call went through a 202 handoff; None otherwise
```

`job_id` is how you see the handoff when it matters. The same endpoint may answer `200` or `202`
depending on how long the work takes, and the SDK hides that on purpose — but if a call took 40
seconds, or you need to correlate it with our logs, that's where the job is named.

Credits are reserved before the work and settled after it. If an operation produces less than
estimated, the difference goes back to your balance — you're billed for what was delivered.

## Links

- API reference — <https://dataswap.io/reference>
- Playground — <https://dataswap.io/dashboard/playground>
- Node/TypeScript SDK — <https://www.npmjs.com/package/dataswap>
- Support — <support@dataswap.io>

## License

MIT
