Metadata-Version: 2.5
Name: infiniteaudience
Version: 0.1.6
Summary: Official Python SDK for the Infinite Audience API — token lifecycle, async file-match orchestration, and typed errors on top of the raw REST surface.
Project-URL: Homepage, https://docs.infiniteaudience.ai
Project-URL: Repository, https://github.com/no-fait/platform
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# infiniteaudience

Official Python SDK for the [Infinite Audience](https://infiniteaudience.ai) API.

**v0.1 — enrichment only.** Audience building (segments, audiences,
campaigns, discovery) is out of scope for this first cut; see the roadmap note
at the bottom.

```bash
pip install infiniteaudience==0.1.6
```

## Why this exists

Three gaps the raw REST API leaves every caller to solve themselves:

1. **Token lifecycle.** API-key mode automatically re-exchanges a `cf_live_`
   key for one-hour bearer tokens. OAuth mode serializes rotating refresh-token
   use and exposes a persistence callback for delegated user applications.
2. **Async workflow orchestration.** File match is create → signed upload →
   poll a status enum → deliver → poll again → fetch a 24h signed URL. Six
   steps, several of which are documented traps (see below) —
   `client.match.file` and `client.deliveries` collapse this into
   `create()` / `wait()` / `download()`.
3. **Result-shape traps.** Microbatch responses are not in input order;
   `iag_person_id` tiers have round-trip semantics; 402 responses carry one of
   two disjoint extras families depending on billing model.

## Quick start

```python
import os
from infinite_audience import InfiniteAudience

client = InfiniteAudience(api_key=os.environ["IA_API_KEY"])

# Microbatch -- re-keyed by row_id, matched/unmatched already partitioned.
result = client.match.microbatch([{"row_id": "1", "email": "jane@example.com"}])
print(result.by_row_id("1"))

# File match -- the match-namespaced happy path, never the audience routes.
job = client.match.file.create(name="Q1 list", file_format="csv")
client.match.file.upload(job, [csv_bytes])
status = client.match.file.wait(job["match_id"])
delivery = client.deliveries.create(job["match_id"], include_unmatched=True)
finished = client.deliveries.wait(job["match_id"], delivery["delivery_id"])
urls = client.deliveries.download(finished)
```

## Result contracts

Real-time results are keyed by `row_id`; they do not echo arbitrary input
columns. File exports preserve source columns. Each supplied `row_id` must
contain a non-whitespace character, and all effective IDs must be unique
within one request, including the position IDs generated for omitted IDs.
Invalid or duplicate IDs reject the whole request before matching.

`MatchResult.data_release` exposes the delivered `{ "major": ..., "minor": ... }`
provenance, with `None` meaning unknown. `billable_identity_count` and
`license_approval_required_count` preserve the API's licensing counts; they
are `None` when an older response omits them. File status and delivery
responses retain provenance as `data_release`. A direct Person ID refresh can
preserve a null prior `match_level`; use `resolution_action` for its outcome.

Catalog dictionaries include aliases, text comparison modes, and whitespace
comparison support. `FilterValidationError` preserves actionable issue paths
and `next_step` guidance for correctable filter requests.

## Timeouts

Ordinary API requests and synchronous `match.microbatch()` calls default to 60
seconds. Set `timeout` for ordinary API requests, `microbatch_timeout` for the
microbatch default, or pass `timeout` to one microbatch call. Values are in
seconds:

```python
client = InfiniteAudience(api_key=os.environ["IA_API_KEY"],
                          timeout=60.0, microbatch_timeout=90.0)
result = client.match.microbatch(records, timeout=120.0)
```

These values are HTTPX read-inactivity limits, not total request deadlines.
Connection, write, and pool waits are capped at 30 seconds or the configured
limit, whichever is shorter. Token exchange and refresh use a separate fixed
60-second timeout. Signed uploads default to a separate 300-second inactivity
timeout, configurable with `upload_timeout`; their connection wait is capped
at 30 seconds.
The timeout for `file.wait()` and `deliveries.wait()` controls the polling
workflow, not any individual HTTP request.

A timed-out microbatch may still complete and be billed by the server. Supply
the same `idempotency_key` when retrying the same request; do not retry a
billable match with a fresh key solely because the client timed out.

## Delegated OAuth

Use OAuth for applications acting with a user's consent. API keys remain the
recommended server-to-server credential. The host application securely stores
the token set and handles the browser redirect; the SDK builds PKCE requests
and refreshes tokens automatically.

```python
from infinite_audience import (
    InfiniteAudience, OAuthTokenManagerOptions, OAuthTokenSet,
    create_authorization_request, exchange_authorization_code,
)

authorization = create_authorization_request(
    client_id="https://client.example/oauth-metadata.json",
    redirect_uri="https://client.example/callback",
    resource="https://api.infiniteaudience.ai",
)
# Open authorization.url and retain authorization.code_verifier until callback.
stored_token_set = exchange_authorization_code(
    client_id="https://client.example/oauth-metadata.json",
    code=callback_code,
    code_verifier=authorization.code_verifier,
    redirect_uri="https://client.example/callback",
    resource="https://api.infiniteaudience.ai",
)
secure_store.save(stored_token_set)

client = InfiniteAudience(oauth=OAuthTokenManagerOptions(
    client_id="https://client.example/oauth-metadata.json",
    token_set=stored_token_set,
    on_token_set=secure_store.save,
))
```

The API URL defaults to `https://api.infiniteaudience.ai`. For an approved
alternate endpoint, pass `base_url` to `InfiniteAudience`. With OAuth, use that
same URL as `resource` and pass its `/v1/oauth/authorize` and `/v1/oauth/token`
URLs as `authorization_endpoint` and `token_endpoint` to the two initial PKCE
helpers. The client then uses `base_url` for token refresh and API calls. Keep
token sets separate for each resource.

MCP and A2A access tokens use their own resource URIs and cannot be replayed
against REST. Standards-compliant MCP clients perform OAuth directly and do
not need this SDK.

## Typed errors

```python
from infinite_audience import InsufficientBalance, PostpayCeilingExceeded, RateLimited
import time

try:
    client.deliveries.create(match_id)
except InsufficientBalance as err:
    print(f"Short ${err.shortfall} (have ${err.available}, need ${err.required})")
except PostpayCeilingExceeded as err:
    print(f"Ceiling ${err.ceiling}, accrued ${err.accrued}")
except RateLimited as err:
    time.sleep(err.retry_after_seconds)
```

## Status

`0.1.0` was the first functional SDK release. `0.1.1` adds `spatial_mode` to
`microbatch()` and `FileMatch.create()`, both of which the raw REST API
already accepted but the SDK did not expose. `0.1.2` fixes `MatchResult` so
derived-only IDs remain in `unmatched` instead of being counted as real matches.
`0.1.3` aligns file-match requests and results with the API, adds saved-match
integration methods, and keeps the client contract checked against the API spec.
`0.1.4` documents the `auto` spatial mode for microbatch and file requests.
`0.1.5` adds configurable API, microbatch, and signed-upload timeouts with a
60-second default for API calls and explicit retry guidance.
`0.1.6` exposes delivered release provenance and licensing counts and adds
typed filter-validation errors with current catalog metadata guidance.
Breaking changes are possible before `1.0.0`. Full docs, including the five
file-match traps this SDK's
`match.file`/`deliveries` design exists to avoid:
<https://docs.infiniteaudience.ai/guides/enrichment-file-match/>.

**Not yet in v0.1:** segments, audiences, campaigns, discovery. These land in
`0.2+` once the enrichment surface above has been exercised end-to-end.
