Metadata-Version: 2.4
Name: cardog
Version: 1.0.0
Summary: Python SDK for the Cardog API — Canadian VIN decode, vehicle specs, live listings, market quotes, and Transport Canada + NHTSA recalls.
Project-URL: Homepage, https://cardog.app
Project-URL: Documentation, https://cardog.app/docs
Project-URL: Repository, https://github.com/cardog-ai/cardog-python
Author-email: Cardog <hello@cardog.app>
License-Expression: MIT
Keywords: api,automotive,canada,cardog,market-data,mcp,recalls,sdk,transport-canada,vehicle,vin,vin-decoder
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
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 :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# cardog

The Python SDK for the Cardog API — the system of record for the Canadian vehicle. Decode VINs, resolve names to permanent refs, search live Canadian listings, quote the market, and check Transport Canada + NHTSA recalls behind one key.

```bash
pip install cardog
```

```python
import os

from cardog import Cardog

client = Cardog(api_key=os.environ["CARDOG_API_KEY"])

# Free text enters exactly once. Everything after this takes refs.
result = client.v2.entities.resolve("2021 Civic")
ref = result.best.ref            # "model-year:honda/civic/2021"

identity = client.v2.vin.get("2HGFC2F53MH500001")
recalls = client.v2.vin.recalls("2HGFC2F53MH500001")
quote = client.v2.quotes.get(ref)
```

Canadian VIN decode coverage is 99.77%. Types and methods are generated from the same OpenAPI contract the API validates against — this SDK and the TypeScript one are siblings, not translations.

Errors are instructions. Every non-2xx v2 response raises a typed `CardogAPIError` carrying `code`, `hint`, `docs_url`, and nearest-ref `suggestions`, so a wrong ref is corrected on the next call instead of silently matched to the wrong vehicle.

Whole platform in one fetch: **https://cardog.app/docs.md**

## Quick start — resolve → refs → query

v2 is ref-native: free text enters the API in exactly one place (`entities.resolve`), everything else speaks canonical refs like `make:tesla` or `model-year:toyota/rav4/2021`.

```python
from cardog import Cardog

client = Cardog(api_key="your-api-key")

# 1. Free text → refs with confidence (the front door)
resolved = client.v2.entities.resolve("tesla model y")
best = resolved.best          # None when nothing clears the confidence floor —
                              # the API never guesses for you
print(best.ref)               # "model:tesla/model-y"

# 2. Dereference a ref: node + parents/children + counts + links
detail = client.v2.entities.get(best.ref)
print(detail.name, detail.counts)

# 3. Query with refs — never fuzzy, never guessed
results = client.v2.listings.search(
    filters={
        "makes": ["make:tesla"],
        "price": {"max": 60000},
        "year": {"min": 2022},
    },
    sort="price",
    order="asc",
)
for listing in results.listings:
    print(f"{listing.year} {listing.make} {listing.model} — ${listing.price:,.0f}")
```

### VIN → identity, recalls, market

```python
# VIN → graph identity card (refs, grains, links)
identity = client.v2.vin.get("5YJSA1E26MF420053")
print(identity.make, identity.model, identity.refs.model_year)

# Batch decode (metered per VIN, max 1000) — one bad VIN fails its own row, never the batch
batch = client.v2.vin.batch(["5YJSA1E26MF420053", "1HGCV1F34LA045661"])

# Recalls affecting a VIN (Transport Canada + NHTSA fused)
recalls = client.v2.recalls.vin("5YJSA1E26MF420053")

# VIN → market instrument bridge, quotes, tape
instrument = client.v2.vin.instrument("5YJSA1E26MF420053")
quote = client.v2.quotes.get("model-year:toyota/rav4/2021")
bars = client.v2.tape.history("model-year:toyota/rav4/2021", window="3m")
```

### Errors are instructions

Every non-2xx v2 response raises `CardogAPIError` carrying the full error envelope — `code` is machine-dispatchable, `hint` says what to do next, and `suggestions` carries nearest-ref candidates so a typo'd ref self-corrects in one turn:

```python
from cardog import Cardog, CardogAPIError

client = Cardog(api_key="your-api-key")

try:
    client.v2.listings.search(filters={"makes": ["make:teslla"]})
except CardogAPIError as e:
    print(e.status_code)   # 400
    print(e.code)          # "unknown_entity_refs"
    print(e.hint)          # "Resolve free text to refs at GET /v2/entities/resolve?q=teslla"
    print(e.docs_url)      # "https://cardog.app/docs/errors#unknown_entity_refs"
    print(e.refs)          # ["make:teslla"]
    for s in e.suggestions or []:
        print(s["invalid"], "→", [n["ref"] for n in s["nearest"]])
        # "make:teslla" → ["make:tesla"]   (advisory — never auto-applied)
```

### Async

`AsyncCardog` mirrors the whole surface:

```python
import asyncio
from cardog import AsyncCardog

async def main():
    client = AsyncCardog(api_key="your-api-key")
    identity = await client.v2.vin.get("5YJSA1E26MF420053")
    results = await client.v2.listings.search(filters={"makes": ["make:tesla"]})
    await client.close()

asyncio.run(main())
```

## The v2 surface

| Group | Methods |
|-------|---------|
| `client.v2.entities` | `browse(domain, ...)` · `resolve(q, ...)` · `get(ref)` |
| `client.v2.vin` | `get(vin)` · `batch(vins)` · `recalls(vin)` · `listings(vin)` · `instrument(vin)` |
| `client.v2.specs` | `catalog()` · `sheet(ref, trim=...)` |
| `client.v2.listings` | `search(...)` · `count(...)` · `facets(...)` · `by_vin(vin)` · `by_id(id)` |
| `client.v2.instruments` | `search(q=..., limit=...)` · `get(ref, window=...)` |
| `client.v2.quotes` | `get(ref)` · `get_many(refs)` |
| `client.v2.tape` | `live(limit=...)` · `history(ref, window=...)` |
| `client.v2.recalls` | `vin(vin)` · `entity(ref)` · `feed()` · `stats()` · `get(ref)` |
| `client.v2.safety` | `ratings(ref)` · `complaints(ref, page=..., limit=...)` |
| platform meta | `client.v2.pricing()` · `client.v2.openapi()` (both unauthenticated) |

The machine-readable rate card (`pricing()`) plus the `X-Credits-*` response headers let you budget mid-task; `openapi()` returns the same contract this SDK is generated from.

## v1 (legacy)

The pre-1.0 resources keep working unchanged — `client.vin.decode(...)`, `client.listings.search(...)`, `client.market`, `client.recalls`, `client.charging`, `client.fuel`, and the rest ride under the same client with the same signatures. New integrations should target `client.v2.*`.

One behavioural note for 1.0: `api_key` is now optional (`Cardog()` works) so the unauthenticated platform-meta routes are reachable without a key. Everything else requires a key, exactly as before.

## Configuration

```python
client = Cardog(
    api_key="your-api-key",          # or omit for pricing()/openapi() only
    base_url="https://api.cardog.app/v1",  # default; a trailing /v1 is stripped for v2 calls
    timeout=30.0,
    max_retries=2,                   # retries 408/429/5xx with backoff, honours Retry-After
)
```

## Regenerating the v2 surface (maintainers)

```bash
make generate    # emit openapi.v2.json from @cardog/contracts, regenerate src/cardog/v2/
make check       # generate twice + assert zero diff + run tests
```

`src/cardog/v2/` (types + group resources) is generated by `scripts/generate.py` from `packages/contracts/dist/openapi.v2.json` — do not edit those files by hand. The method map (operationId → Python method) lives in the generator and is validated against the spec on every run.

## License

MIT

---

mcp-name: app.cardog/mcp
