Metadata-Version: 2.4
Name: stormwateriq-api-client
Version: 0.2.0
Summary: Official Python client for the StormwaterIQ external API (typed client, webhook signature verifier, retry helpers).
Project-URL: Homepage, https://github.com/stewartmoreland/stormwater-iq/tree/main/packages/sdk-py#readme
Project-URL: Repository, https://github.com/stewartmoreland/stormwater-iq
Project-URL: Issues, https://github.com/stewartmoreland/stormwater-iq/issues
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Description-Content-Type: text/markdown

# stormwateriq-api-client

Official Python client for the StormwaterIQ external API: a typed-ish client, the
reference webhook-signature verifier, and automatic retries.

```bash
pip install stormwateriq-api-client
```

```python
from stormwateriq_api_client import StormwaterIQClient, verify

with StormwaterIQClient("https://api.stormwateriq.com", access_token) as client:
    site = client.get_site("site_123")
    page = client.list_inspections(site_id="site_123", limit=50)
    created = client.create_site(
        {"name": "Lot 7", "longitude": -84.39, "latitude": 33.77, "bmp_type": "dumpster_enclosure"},
        idempotency_key="install_42",  # echoed as client_reference + in site.registered
    )

# Verify an inbound webhook (constant-time, replay-protected):
ok = verify(raw_body, request.headers["X-StormwaterIQ-Signature"], signing_secret)
```

The client covers sites (`get`/`list`/`create`/`update`), inspections
(`get`/`list`/`photos`/`report`), webhooks (`register`/`list`/`delete`), sponsor
outcomes, and health.

### Authentication (`TokenSource`)

Mint + cache client-credentials tokens (refreshed ahead of expiry) and hand the
getter to the client so the bearer stays current:

```python
import os
from stormwateriq_api_client import StormwaterIQClient, TokenSource

tokens = TokenSource(
    os.environ["SWIQ_CLIENT_ID"],
    os.environ["SWIQ_CLIENT_SECRET"],
    scopes=["site:read:owned", "site:write:owned"],
)
client = StormwaterIQClient(token=tokens.get_token)  # base_url defaults to production
```

### Typed webhook events

After verifying, parse the body into a validated envelope and branch on `type`:

```python
from stormwateriq_api_client import verify, parse_event

if not verify(raw_body, sig_header, signing_secret):
    return 400
event = parse_event(raw_body)  # {"id", "type", "created_at", "data"}
if event["type"] == "site.registered":
    link(event["data"]["site_id"], event["data"]["client_reference"])
```

The delivered body is the signed envelope `{id, type, created_at, data}` — `id`
is the stable per-event dedup key; `type` mirrors the `X-StormwaterIQ-Event` header.

- **Retries** — requests retry 429 (honoring `Retry-After`) and 5xx with exponential
  backoff (`max_retries`, default 3); other 4xx raise `ApiError`.
- **Webhook verifier** — `verify(payload, signature, secret)` is stdlib-only
  (`hmac`/`hashlib`) and byte-identical to the server and the TypeScript SDK, pinned to
  `fixtures/webhook-signature-vectors.json`. Importing the verifier (and `parse_event`)
  never requires `httpx` (the client is lazily imported).

## Generation

Models and methods are hand-authored to mirror the gateway's OpenAPI document
(`https://api.stormwateriq.com/v1/openapi.json`); keep `models.py` /
`webhook_events.py` in sync when the spec changes. Published independently of the
API (semver).
