# Spaw Python SDK: verify emails from Python

The official spaw-sdk package wraps every Spaw endpoint with typed results and one exception class per API code. Standard library only, Python 3.9 or newer.

Updated: 2026-09-03

The Python client is a thin wrapper over the REST API: one method per endpoint, the API's `data` and `meta` blocks returned exactly as documented, and a typed exception for every error code. It has no dependencies beyond the standard library, ships type hints, and works on Python 3.9 and newer.

The package is built and will be published on PyPI as `spaw-sdk`; the distribution name differs from the import name because `spaw` was already taken. Until the listing is live, the plain REST calls in the API reference do the same job with a few more lines.

## Install and verify one address

1. Create a secret key on the API keys page.
2. Install the package once it is published:

```bash
pip install spaw-sdk
```

3. Verify an address. The import name is `spaw`:

```python
from spaw import Client

client = Client("sk_live_…")

result = client.verify_email("mia@acme.com")
print(result.data["deliverable"])   # "deliverable", "risky" or "undeliverable"
print(result.data["reason"])        # e.g. "catch_all", or None when deliverable
print(result.data["risk_score"])    # 0–100, a sum of published weights
print(result.meta["credits_used"])  # 1 for a fresh answered lookup, 0 otherwise
```

Every `reason` and `smtp_reason` value is explained on the verdict reasons pages, and `result.data` carries all 27 documented fields, including `did_you_mean`, `mx_provider` and `smtp_checked_at`.

## Everything the client does

| Method | Endpoint |
| --- | --- |
| `verify_email(email, callback_url=None, callback_secret=None)` | `POST /email` |
| `verify_emails(emails)` | `POST /email/batch`, up to 50 |
| `domain(domain)` | `GET /email/domain/{domain}` |
| `create_bulk_job(emails, webhook_url=None)`, `get_bulk_job(id)`, `cancel_bulk_job(id)`, `download_bulk_results(id, variant=None)` | the bulk endpoints, up to 100,000 addresses |
| `list_suppressions(page=1)`, `add_suppressions(emails)`, `remove_suppression(id)` | the suppression list |
| `report_feedback(items)`, `feedback_summary()` | delivery outcomes and measured accuracy |
| `account()` | the live credit balance |
| `validate_phone(phone, country=None)`, `lookup_ip(ip)` | the phone and IP endpoints |

A batch keeps the API's shape: `data["results"]` holds one `{data, meta}` per address in input order, and `meta["stopped_reason"]` is set instead of an exception when the balance runs out part-way, so paid results are never lost.

## Handle errors

Every failure raises a subclass of `SpawError` carrying `code`, `message`, `http_status` and `request_id`; no credits are spent on a failed request.

```python
from spaw import InsufficientCreditsError, RateLimitedError, SpawError

try:
    result = client.verify_email("mia@acme.com")
except InsufficientCreditsError:
    ...  # nothing ran and nothing was billed
except RateLimitedError as error:
    ...  # error.retry_after seconds, from the Retry-After header
except SpawError as error:
    print(error.code, error.http_status, error.request_id)
```

`AuthenticationError` maps `UNAUTHENTICATED`, `ValidationError` maps `VALIDATION_FAILED` with `errors` keyed by field, `NotFoundError` covers a bulk job or suppression entry that is not on the account, and `SpawConnectionError` means the API could not be reached. Pass `max_retries=2` to the client to retry `429` and `5xx` answers automatically, honouring `Retry-After`.

## What to do with the verdict

Branch on `deliverable`. Keep `deliverable` addresses, drop `undeliverable` ones, and route `risky` ones by `reason`: `disposable` and `likely_typo` are usually refused, `role` depends on your use case, and `catch_all` is often a fine address whose mailbox simply cannot be confirmed, in which case `mailbox_confidence` gives you a number to threshold. Offer `did_you_mean` back to the user when it is set.

## Cost

One credit per fresh deliverable or risky verdict. Undeliverable verdicts, invalid input, repeats within seven days and the `spaw.test` test addresses cost nothing, and every account gets 10 free lookups a month. Use `deliverable@spaw.test` and its siblings in your test suite: they answer fixed results, cost nothing and never appear in your history.

Reference: https://spaw.co/integrations/python-sdk
