Metadata-Version: 2.4
Name: aranova-tracking
Version: 0.3.0
Summary: Typed Python client for the Aranova tracking API (sales, customers, calendar, config).
License-Expression: MIT
License-File: LICENSE
Keywords: aranova,tracking,sales,analytics,sdk
Author: Aranova
Author-email: ritesh@aranova.io
Requires-Python: >=3.11
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Classifier: Intended Audience :: Developers
Requires-Dist: httpx (>=0.27,<1)
Requires-Dist: pydantic[email] (>=2.7,<3)
Requires-Dist: tenacity (>=8,<10)
Project-URL: Homepage, https://aranova.io
Description-Content-Type: text/markdown

# aranova-tracking

Typed Python client for the Aranova tracking API — record sales, read the ledger and
customer roster, manage calendar bookings, and fetch a business's tracking config from
your own backend.

```bash
pip install aranova-tracking
```

## Quick start

```python
import os
from aranova_tracking import AranovaTracking
from aranova_tracking.models import SaleCreateSchema

with AranovaTracking(os.environ["ARANOVA_TRACKING_SECRET_KEY"]) as aranova:
    sale = aranova.tracking_sales.record_sale(
        SaleCreateSchema(
            external_id="order-1042",      # your id — makes this call idempotent
            currency="CAD",
            amount_total_cents=12_000,     # minor units: $120.00
            occurred_at="2026-09-09T14:03:00Z",
            customer_phone="+14165550123",
        )
    )
    print(sale.id)
```

Async is the same surface:

```python
from aranova_tracking import AsyncAranovaTracking

async with AsyncAranovaTracking(secret_key) as aranova:
    page = await aranova.tracking_sales.query_sales(...)
```

## Your API key

Use a **secret** key (`aranv_sk_…`), issued per business in the Aranova dashboard, and
keep it in server-side env only. A public key (`aranv_pk_…`) authenticates but may only
*create* sales — every read returns 403. Never ship either key in a browser bundle.

## Idempotency

`external_id` is your dedup handle. Send the same one twice and the second call returns
the **existing** sale with HTTP 200 instead of creating a duplicate (a fresh create is
201). Omit it and there is no dedup at all — a retried request writes a second sale.

## Errors

Every failure raises a subclass of `AranovaAPIError` carrying a stable `code`, the
`request_id` to quote when reporting a problem, and any extra `context`:

```python
from aranova_tracking import AranovaAPIError, RateLimitError, ValidationError

try:
    aranova.tracking_sales.record_sale(payload)
except ValidationError as exc:
    print(exc.errors)          # per-field failures
except RateLimitError as exc:
    print(exc.retry_after)     # seconds, from the server
except AranovaAPIError as exc:
    print(exc.code, exc.request_id)
```

Branch on `exc.code`, never on the message text — prose is free to change, codes are
contract. Transport failures, 429s and 5xx are retried automatically with exponential
backoff; 4xx you must fix are raised immediately.

## Models are real Pydantic

`aranova_tracking.models` is generated from the API's own schemas, so the models are
genuine `pydantic.BaseModel` classes — usable directly as a FastAPI `response_model`,
and they validate. They also tolerate unknown fields, so a client pinned to an older
release keeps working when the API adds one.

## Versioning

Semantic versioning on the wire contract: a new optional field is a patch, a new
endpoint a minor, and anything removed or retyped a major. Pin a major
(`aranova-tracking = "^1.0"`) and upgrade on your own schedule.

---

Generated from `apps/api/openapi.json`; `models.py` and `_endpoints.py` are build
artifacts — see `docs/runbooks/python-sdk.md` in the monorepo.

