cardog 2.1.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cardog-2.1.0 → cardog-2.2.0}/CHANGELOG.md +30 -0
- {cardog-2.1.0 → cardog-2.2.0}/PKG-INFO +26 -16
- {cardog-2.1.0 → cardog-2.2.0}/README.md +25 -15
- {cardog-2.1.0 → cardog-2.2.0}/openapi/openapi.v2.json +1165 -231
- {cardog-2.1.0 → cardog-2.2.0}/pyproject.toml +1 -1
- {cardog-2.1.0 → cardog-2.2.0}/scripts/generate.py +28 -7
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/__init__.py +8 -2
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/_base_client.py +23 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/_client.py +1 -1
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/_constants.py +1 -1
- cardog-2.2.0/src/cardog/_credits.py +74 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/_exceptions.py +10 -2
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/__init__.py +5 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/entities.py +8 -2
- cardog-2.2.0/src/cardog/v2/feeds.py +107 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/instruments.py +8 -2
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/quotes.py +8 -2
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/recalls.py +8 -2
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/types.py +124 -1
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/vin.py +8 -2
- {cardog-2.1.0 → cardog-2.2.0}/tests/conftest.py +1 -1
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_client.py +15 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_async.py +4 -4
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_vin.py +4 -4
- {cardog-2.1.0 → cardog-2.2.0}/tests/v2/spec_fixtures.py +1 -1
- cardog-2.2.0/tests/v2/test_v2_credits.py +125 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/v2/test_v2_live_smoke.py +3 -3
- {cardog-2.1.0 → cardog-2.2.0}/tests/v2/test_v2_urls.py +16 -7
- {cardog-2.1.0 → cardog-2.2.0}/.gitignore +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/Makefile +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/_async_client.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/py.typed +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/__init__.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/charging.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/complaints.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/efficiency.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/fuel.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/history.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/listings.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/locations.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/market.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/recalls.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/research.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/safety.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/resources/vin.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/__init__.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/charging.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/common.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/complaints.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/efficiency.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/fuel.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/history.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/listings.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/locations.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/market.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/recalls.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/research.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/safety.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/types/vin.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/_support.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/charging.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/feedback.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/listings.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/platform.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/safety.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/specs.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/src/cardog/v2/tape.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/__init__.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_charging.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_complaints.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_efficiency.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_fuel.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_listings.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_locations.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_market.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_recalls.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_research.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/test_contracts_safety.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/v2/__init__.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/v2/test_v2_async.py +0 -0
- {cardog-2.1.0 → cardog-2.2.0}/tests/v2/test_v2_errors.py +0 -0
|
@@ -8,6 +8,36 @@ pre-release it tracks. 3.0.0, published to PyPI on 2026-09-28, broke that
|
|
|
8
8
|
rule and is yanked: `pip install cardog` no longer resolves to it, and a
|
|
9
9
|
pin on `==3.0.0` still installs. Everything it carried is in 2.1.0.
|
|
10
10
|
|
|
11
|
+
## [2.2.0] - 2026-09-30
|
|
12
|
+
|
|
13
|
+
Tracks API 2.0.0-rc.6. The SDK ergonomics batch (CRDG-3286). Nothing on the
|
|
14
|
+
wire changes; nothing existing stops working.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `client.last_credits`: the `X-Credits-*` headers of the last metered
|
|
19
|
+
response, parsed into a `Credits` (`rate`, `remaining`, `allowance`,
|
|
20
|
+
`reset`, `source`), on both `Cardog` and `AsyncCardog`. Errors included: a
|
|
21
|
+
402 states the budget too. `None` until a metered response arrives, and an
|
|
22
|
+
unmetered response (pricing, a signed link) leaves the last set alone. The
|
|
23
|
+
credits contract says the price of your next call is in the headers of your
|
|
24
|
+
last one; budget-aware callers no longer drop to raw httpx for it.
|
|
25
|
+
- `APIError.message` (and so `CardogAPIError.message`): the API's message on
|
|
26
|
+
its own. `str(err)` is unchanged, `[status] (code) message`, so
|
|
27
|
+
`f"{err.code}: {err.message}"` prints the code once.
|
|
28
|
+
- The sibling names the TypeScript client uses, on the same groups:
|
|
29
|
+
`client.v2.entities.get_by_ref`, `client.v2.instruments.get_by_ref`,
|
|
30
|
+
`client.v2.quotes.get_by_ref`, `client.v2.recalls.get_by_ref` and
|
|
31
|
+
`client.v2.vin.get_by_vin` (for `getByRef` / `getByVin`). The former `get`
|
|
32
|
+
on each group stays callable as an alias.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- The documented example VIN is `5YJSA1E28MF420053` everywhere (README,
|
|
37
|
+
docstrings, tests). The previous `5YJSA1E26MF420053` fails the check digit
|
|
38
|
+
at position 9; the decoder tolerated it, but an integrator validating check
|
|
39
|
+
digits first rejected our own example.
|
|
40
|
+
|
|
11
41
|
## [2.1.0] - 2026-09-30
|
|
12
42
|
|
|
13
43
|
Tracks API 2.0.0-rc.6. Carries what 3.0.0 (yanked) and the unpublished
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cardog
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Python SDK for the Cardog API — Canadian VIN decode, vehicle specs, live listings, market quotes, and Transport Canada + NHTSA recalls.
|
|
5
5
|
Project-URL: Homepage, https://cardog.app
|
|
6
6
|
Project-URL: Documentation, https://cardog.app/docs
|
|
@@ -51,9 +51,9 @@ client = Cardog(api_key=os.environ["CARDOG_API_KEY"])
|
|
|
51
51
|
result = client.v2.entities.resolve("2021 Civic")
|
|
52
52
|
ref = result.best.ref # "model-year:honda/civic/2021"
|
|
53
53
|
|
|
54
|
-
identity = client.v2.vin.
|
|
54
|
+
identity = client.v2.vin.get_by_vin("2HGFC2F53MH500001")
|
|
55
55
|
recalls = client.v2.vin.recalls("2HGFC2F53MH500001")
|
|
56
|
-
quote = client.v2.quotes.
|
|
56
|
+
quote = client.v2.quotes.get_by_ref(ref)
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
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.
|
|
@@ -78,7 +78,7 @@ best = resolved.best # None when nothing clears the confidence floor
|
|
|
78
78
|
print(best.ref) # "model:tesla/model-y"
|
|
79
79
|
|
|
80
80
|
# 2. Dereference a ref: node + parents/children + counts + links
|
|
81
|
-
detail = client.v2.entities.
|
|
81
|
+
detail = client.v2.entities.get_by_ref(best.ref)
|
|
82
82
|
print(detail.name, detail.counts)
|
|
83
83
|
|
|
84
84
|
# 3. Query with refs — never fuzzy, never guessed
|
|
@@ -99,18 +99,18 @@ for listing in results.listings:
|
|
|
99
99
|
|
|
100
100
|
```python
|
|
101
101
|
# VIN → graph identity card (refs, grains, links)
|
|
102
|
-
identity = client.v2.vin.
|
|
102
|
+
identity = client.v2.vin.get_by_vin("5YJSA1E28MF420053")
|
|
103
103
|
print(identity.make, identity.model, identity.refs.model_year)
|
|
104
104
|
|
|
105
105
|
# Batch decode (metered per VIN, max 1000) — one bad VIN fails its own row, never the batch
|
|
106
|
-
batch = client.v2.vin.batch(["
|
|
106
|
+
batch = client.v2.vin.batch(["5YJSA1E28MF420053", "1HGCV1F34LA045661"])
|
|
107
107
|
|
|
108
108
|
# Recalls affecting a VIN (Transport Canada + NHTSA fused)
|
|
109
|
-
recalls = client.v2.recalls.vin("
|
|
109
|
+
recalls = client.v2.recalls.vin("5YJSA1E28MF420053")
|
|
110
110
|
|
|
111
111
|
# VIN → market instrument bridge, quotes, tape
|
|
112
|
-
instrument = client.v2.vin.instrument("
|
|
113
|
-
quote = client.v2.quotes.
|
|
112
|
+
instrument = client.v2.vin.instrument("5YJSA1E28MF420053")
|
|
113
|
+
quote = client.v2.quotes.get_by_ref("model-year:toyota/rav4/2021")
|
|
114
114
|
bars = client.v2.tape.history("model-year:toyota/rav4/2021", window="3m")
|
|
115
115
|
```
|
|
116
116
|
|
|
@@ -146,7 +146,7 @@ from cardog import AsyncCardog
|
|
|
146
146
|
|
|
147
147
|
async def main():
|
|
148
148
|
client = AsyncCardog(api_key="your-api-key")
|
|
149
|
-
identity = await client.v2.vin.
|
|
149
|
+
identity = await client.v2.vin.get_by_vin("5YJSA1E28MF420053")
|
|
150
150
|
results = await client.v2.listings.search(filters={"makes": ["make:tesla"]})
|
|
151
151
|
await client.close()
|
|
152
152
|
|
|
@@ -157,18 +157,28 @@ asyncio.run(main())
|
|
|
157
157
|
|
|
158
158
|
| Group | Methods |
|
|
159
159
|
|-------|---------|
|
|
160
|
-
| `client.v2.entities` | `browse(domain, ...)` · `resolve(q, ...)` · `
|
|
161
|
-
| `client.v2.vin` | `
|
|
160
|
+
| `client.v2.entities` | `browse(domain, ...)` · `resolve(q, ...)` · `get_by_ref(ref)` |
|
|
161
|
+
| `client.v2.vin` | `get_by_vin(vin)` · `batch(vins)` · `recalls(vin)` · `listings(vin)` · `instrument(vin)` |
|
|
162
162
|
| `client.v2.specs` | `catalog()` · `sheet(ref, trim=...)` |
|
|
163
163
|
| `client.v2.listings` | `search(...)` · `count(...)` · `facets(...)` · `by_vin(vin)` · `by_id(id)` |
|
|
164
|
-
| `client.v2.instruments` | `search(q=..., limit=...)` · `
|
|
165
|
-
| `client.v2.quotes` | `
|
|
164
|
+
| `client.v2.instruments` | `search(q=..., limit=...)` · `get_by_ref(ref, window=...)` |
|
|
165
|
+
| `client.v2.quotes` | `get_by_ref(ref)` · `get_many(refs)` |
|
|
166
166
|
| `client.v2.tape` | `live(limit=...)` · `history(ref, window=...)` |
|
|
167
|
-
| `client.v2.recalls` | `vin(vin)` · `entity(ref)` · `feed()` · `stats()` · `
|
|
167
|
+
| `client.v2.recalls` | `vin(vin)` · `entity(ref)` · `feed()` · `stats()` · `get_by_ref(ref)` |
|
|
168
168
|
| `client.v2.safety` | `ratings(ref)` · `complaints(ref, page=..., limit=...)` |
|
|
169
169
|
| platform meta | `client.v2.pricing()` · `client.v2.openapi()` (both unauthenticated) |
|
|
170
170
|
|
|
171
|
-
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.
|
|
171
|
+
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. The client keeps the last set of those headers parsed on `client.last_credits`, errors included (a 402 states the budget too):
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
client.v2.vin.get_by_vin("5YJSA1E28MF420053")
|
|
175
|
+
credits = client.last_credits # a Credits, or None before any metered call
|
|
176
|
+
print(credits.rate, credits.remaining, credits.allowance, credits.reset, credits.source)
|
|
177
|
+
# rate: what that call cost · remaining: allowance headroom + pack balance (-1 = custom plan)
|
|
178
|
+
# reset: when the allowance rolls over · source: "allowance" | "pack" | "overage", None when nothing was debited
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Errors carry the envelope: `err.code` to branch on, `err.message` on its own, `err.hint` for what to do next. `str(err)` is the log line, `[status] (code) message`.
|
|
172
182
|
|
|
173
183
|
## v1 (legacy)
|
|
174
184
|
|
|
@@ -17,9 +17,9 @@ client = Cardog(api_key=os.environ["CARDOG_API_KEY"])
|
|
|
17
17
|
result = client.v2.entities.resolve("2021 Civic")
|
|
18
18
|
ref = result.best.ref # "model-year:honda/civic/2021"
|
|
19
19
|
|
|
20
|
-
identity = client.v2.vin.
|
|
20
|
+
identity = client.v2.vin.get_by_vin("2HGFC2F53MH500001")
|
|
21
21
|
recalls = client.v2.vin.recalls("2HGFC2F53MH500001")
|
|
22
|
-
quote = client.v2.quotes.
|
|
22
|
+
quote = client.v2.quotes.get_by_ref(ref)
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
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.
|
|
@@ -44,7 +44,7 @@ best = resolved.best # None when nothing clears the confidence floor
|
|
|
44
44
|
print(best.ref) # "model:tesla/model-y"
|
|
45
45
|
|
|
46
46
|
# 2. Dereference a ref: node + parents/children + counts + links
|
|
47
|
-
detail = client.v2.entities.
|
|
47
|
+
detail = client.v2.entities.get_by_ref(best.ref)
|
|
48
48
|
print(detail.name, detail.counts)
|
|
49
49
|
|
|
50
50
|
# 3. Query with refs — never fuzzy, never guessed
|
|
@@ -65,18 +65,18 @@ for listing in results.listings:
|
|
|
65
65
|
|
|
66
66
|
```python
|
|
67
67
|
# VIN → graph identity card (refs, grains, links)
|
|
68
|
-
identity = client.v2.vin.
|
|
68
|
+
identity = client.v2.vin.get_by_vin("5YJSA1E28MF420053")
|
|
69
69
|
print(identity.make, identity.model, identity.refs.model_year)
|
|
70
70
|
|
|
71
71
|
# Batch decode (metered per VIN, max 1000) — one bad VIN fails its own row, never the batch
|
|
72
|
-
batch = client.v2.vin.batch(["
|
|
72
|
+
batch = client.v2.vin.batch(["5YJSA1E28MF420053", "1HGCV1F34LA045661"])
|
|
73
73
|
|
|
74
74
|
# Recalls affecting a VIN (Transport Canada + NHTSA fused)
|
|
75
|
-
recalls = client.v2.recalls.vin("
|
|
75
|
+
recalls = client.v2.recalls.vin("5YJSA1E28MF420053")
|
|
76
76
|
|
|
77
77
|
# VIN → market instrument bridge, quotes, tape
|
|
78
|
-
instrument = client.v2.vin.instrument("
|
|
79
|
-
quote = client.v2.quotes.
|
|
78
|
+
instrument = client.v2.vin.instrument("5YJSA1E28MF420053")
|
|
79
|
+
quote = client.v2.quotes.get_by_ref("model-year:toyota/rav4/2021")
|
|
80
80
|
bars = client.v2.tape.history("model-year:toyota/rav4/2021", window="3m")
|
|
81
81
|
```
|
|
82
82
|
|
|
@@ -112,7 +112,7 @@ from cardog import AsyncCardog
|
|
|
112
112
|
|
|
113
113
|
async def main():
|
|
114
114
|
client = AsyncCardog(api_key="your-api-key")
|
|
115
|
-
identity = await client.v2.vin.
|
|
115
|
+
identity = await client.v2.vin.get_by_vin("5YJSA1E28MF420053")
|
|
116
116
|
results = await client.v2.listings.search(filters={"makes": ["make:tesla"]})
|
|
117
117
|
await client.close()
|
|
118
118
|
|
|
@@ -123,18 +123,28 @@ asyncio.run(main())
|
|
|
123
123
|
|
|
124
124
|
| Group | Methods |
|
|
125
125
|
|-------|---------|
|
|
126
|
-
| `client.v2.entities` | `browse(domain, ...)` · `resolve(q, ...)` · `
|
|
127
|
-
| `client.v2.vin` | `
|
|
126
|
+
| `client.v2.entities` | `browse(domain, ...)` · `resolve(q, ...)` · `get_by_ref(ref)` |
|
|
127
|
+
| `client.v2.vin` | `get_by_vin(vin)` · `batch(vins)` · `recalls(vin)` · `listings(vin)` · `instrument(vin)` |
|
|
128
128
|
| `client.v2.specs` | `catalog()` · `sheet(ref, trim=...)` |
|
|
129
129
|
| `client.v2.listings` | `search(...)` · `count(...)` · `facets(...)` · `by_vin(vin)` · `by_id(id)` |
|
|
130
|
-
| `client.v2.instruments` | `search(q=..., limit=...)` · `
|
|
131
|
-
| `client.v2.quotes` | `
|
|
130
|
+
| `client.v2.instruments` | `search(q=..., limit=...)` · `get_by_ref(ref, window=...)` |
|
|
131
|
+
| `client.v2.quotes` | `get_by_ref(ref)` · `get_many(refs)` |
|
|
132
132
|
| `client.v2.tape` | `live(limit=...)` · `history(ref, window=...)` |
|
|
133
|
-
| `client.v2.recalls` | `vin(vin)` · `entity(ref)` · `feed()` · `stats()` · `
|
|
133
|
+
| `client.v2.recalls` | `vin(vin)` · `entity(ref)` · `feed()` · `stats()` · `get_by_ref(ref)` |
|
|
134
134
|
| `client.v2.safety` | `ratings(ref)` · `complaints(ref, page=..., limit=...)` |
|
|
135
135
|
| platform meta | `client.v2.pricing()` · `client.v2.openapi()` (both unauthenticated) |
|
|
136
136
|
|
|
137
|
-
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.
|
|
137
|
+
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. The client keeps the last set of those headers parsed on `client.last_credits`, errors included (a 402 states the budget too):
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
client.v2.vin.get_by_vin("5YJSA1E28MF420053")
|
|
141
|
+
credits = client.last_credits # a Credits, or None before any metered call
|
|
142
|
+
print(credits.rate, credits.remaining, credits.allowance, credits.reset, credits.source)
|
|
143
|
+
# rate: what that call cost · remaining: allowance headroom + pack balance (-1 = custom plan)
|
|
144
|
+
# reset: when the allowance rolls over · source: "allowance" | "pack" | "overage", None when nothing was debited
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Errors carry the envelope: `err.code` to branch on, `err.message` on its own, `err.hint` for what to do next. `str(err)` is the log line, `[status] (code) message`.
|
|
138
148
|
|
|
139
149
|
## v1 (legacy)
|
|
140
150
|
|