tarifftrust 0.1.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.
- tarifftrust-0.1.0/.gitignore +12 -0
- tarifftrust-0.1.0/LICENSE +21 -0
- tarifftrust-0.1.0/PKG-INFO +223 -0
- tarifftrust-0.1.0/README.md +194 -0
- tarifftrust-0.1.0/pyproject.toml +70 -0
- tarifftrust-0.1.0/src/tarifftrust/__init__.py +39 -0
- tarifftrust-0.1.0/src/tarifftrust/_client.py +326 -0
- tarifftrust-0.1.0/src/tarifftrust/_errors.py +105 -0
- tarifftrust-0.1.0/src/tarifftrust/_model.py +89 -0
- tarifftrust-0.1.0/src/tarifftrust/_operations.py +870 -0
- tarifftrust-0.1.0/src/tarifftrust/_version.py +3 -0
- tarifftrust-0.1.0/src/tarifftrust/py.typed +0 -0
- tarifftrust-0.1.0/src/tarifftrust/types.py +1651 -0
- tarifftrust-0.1.0/tests/conftest.py +64 -0
- tarifftrust-0.1.0/tests/test_client.py +413 -0
- tarifftrust-0.1.0/tests/test_live.py +130 -0
- tarifftrust-0.1.0/tests/test_model.py +122 -0
- tarifftrust-0.1.0/uv.lock +561 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sébastien Chapeland EI (TariffTrust)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tarifftrust
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for the TariffTrust API: every US import duty and fee for an HTS code, an origin and a value, each with its official source.
|
|
5
|
+
Project-URL: Homepage, https://tarifftrust.com/api/
|
|
6
|
+
Project-URL: Documentation, https://tarifftrust.com/api/reference/
|
|
7
|
+
Project-URL: Guides, https://tarifftrust.com/guides/
|
|
8
|
+
Project-URL: Repository, https://github.com/tarifftrust/tarifftrust
|
|
9
|
+
Project-URL: Changelog, https://tarifftrust.com/changelog/
|
|
10
|
+
Author-email: TariffTrust <contact@tarifftrust.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: api-client,customs,harmonized-tariff-schedule,hts,import-duty,landed-cost,section-232,section-301,tariff,tariffs,trade-compliance
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: httpx<1,>=0.25
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# tarifftrust: US tariff API client for Python
|
|
31
|
+
|
|
32
|
+
The official Python client for the [TariffTrust API](https://tarifftrust.com/api/). Send an HTS code,
|
|
33
|
+
a country of origin and a value. Get every US import duty and fee that applies, from the tariff
|
|
34
|
+
schedule and Sections 301, 232, 201 and 338 to the entry fees, each with its official, dated source.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install tarifftrust
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires Python 3.10 or later. Fully typed, sync and async.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
Create a key in [your workspace](https://tarifftrust.com/keys/); your plan must include the API
|
|
45
|
+
([plans](https://tarifftrust.com/pricing/)). Keep it on your server.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from tarifftrust import TariffTrust
|
|
49
|
+
|
|
50
|
+
client = TariffTrust(api_key="tarifftrust_...") # or set TARIFFTRUST_API_KEY
|
|
51
|
+
|
|
52
|
+
result = client.estimate(hts="6109.10.00.12", origin="CN", customs_value="10000")
|
|
53
|
+
|
|
54
|
+
print(result.status, result.estimated_duty)
|
|
55
|
+
for component in result.components:
|
|
56
|
+
print(component.label, component.amount, component.source.url)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Read `status` first, then the amounts: decimal strings in US dollars, ready for `Decimal`.
|
|
60
|
+
|
|
61
|
+
**Unknown is never zero.** When `estimated_duty` is `None`, the duty could not be established: only
|
|
62
|
+
`known_duty_subtotal` is. See [the guide](https://tarifftrust.com/guides/#answer).
|
|
63
|
+
|
|
64
|
+
## A whole declaration
|
|
65
|
+
|
|
66
|
+
MPF and HMF are charged once on a declaration: adding up single-product estimates gives a wrong total.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
entry = client.estimate_entry(
|
|
70
|
+
transport_mode="ocean",
|
|
71
|
+
lines=[
|
|
72
|
+
{"reference": "PO-1042-1", "hts": "6109.10.00.12", "origin": "CN", "customs_value": "1000"},
|
|
73
|
+
{"reference": "PO-1042-2", "hts": "6109.10.00.12", "origin": "VN", "customs_value": "2500"},
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
print(entry.estimated_duty, entry.total_fees, entry.estimated_landed_cost)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Several products, a history, a saved estimate
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
batch = client.estimate_batch(
|
|
83
|
+
items=[
|
|
84
|
+
{"hts": "6109.10.00.12", "origin": "CN", "customs_value": "10000"},
|
|
85
|
+
{"hts": "6109.10.00.12", "origin": "VN", "customs_value": "10000"},
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
for item in batch.results:
|
|
89
|
+
print(item.index, item.result.estimated_duty if hasattr(item, "result") else item.error.code)
|
|
90
|
+
|
|
91
|
+
history = client.estimate_history(
|
|
92
|
+
hts="6109.10.00.12", origin="CN", customs_value="10000", from_="2026-10-01"
|
|
93
|
+
)
|
|
94
|
+
for point in history.points:
|
|
95
|
+
print(point.as_of, point.estimated_duty)
|
|
96
|
+
|
|
97
|
+
saved = client.save_estimate(
|
|
98
|
+
estimate={"hts": "6109.10.00.12", "origin": "CN", "customs_value": "10000"}
|
|
99
|
+
)
|
|
100
|
+
print(saved.id)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`from` is a reserved word in Python: it is `from_` here, as an argument and on an answer.
|
|
104
|
+
|
|
105
|
+
## The catalog
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
found = client.search_hts(q="cotton t-shirt", limit=5)
|
|
109
|
+
line = client.get_hts("6109.10.00.12")
|
|
110
|
+
print(line.description, line.general)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Async
|
|
114
|
+
|
|
115
|
+
`AsyncTariffTrust` has the same methods, awaited.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import asyncio
|
|
119
|
+
from tarifftrust import AsyncTariffTrust
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def main():
|
|
123
|
+
async with AsyncTariffTrust() as client:
|
|
124
|
+
return await asyncio.gather(
|
|
125
|
+
client.estimate(hts="6109.10.00.12", origin="CN", customs_value="10000"),
|
|
126
|
+
client.estimate(hts="6109.10.00.12", origin="VN", customs_value="10000"),
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Errors
|
|
131
|
+
|
|
132
|
+
Each refusal of the API is its own exception, under `APIError`.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from tarifftrust import APIError, InvalidInputError, RateLimitError
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
result = client.estimate(hts="6109.10.00.12", origin="??", customs_value="10000")
|
|
139
|
+
except InvalidInputError as error:
|
|
140
|
+
print(error.fields) # each field refused
|
|
141
|
+
except APIError as error:
|
|
142
|
+
print(error.status, error.code, error.message, error.request_id)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| Exception | Status | When |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| `AuthenticationError` | 401 | No valid API key |
|
|
148
|
+
| `AccessRequiredError` | 402 | No active subscription, or the quota is used up (`quota_exceeded`) |
|
|
149
|
+
| `PermissionDeniedError` | 403 | The key lacks the permission, or the plan the API |
|
|
150
|
+
| `NotFoundError` | 404 | Nothing of that name |
|
|
151
|
+
| `ConflictError` | 409 | An idempotency key sent again with another request |
|
|
152
|
+
| `PayloadTooLargeError` | 413 | Send fewer products or lines |
|
|
153
|
+
| `InvalidInputError` | 400, 422 | `fields` names each field refused; a 400 is a body that is not valid JSON |
|
|
154
|
+
| `RateLimitError` | 429 | Too many requests |
|
|
155
|
+
| `ServiceUnavailableError` | 502, 503, 504 | Unavailable for now |
|
|
156
|
+
| `APIConnectionError`, `APITimeoutError` | | The API could not be reached |
|
|
157
|
+
|
|
158
|
+
All of them inherit from `TariffTrustError`.
|
|
159
|
+
|
|
160
|
+
## Retries and idempotency
|
|
161
|
+
|
|
162
|
+
A call is tried again, twice by default, after a connection failure, a timeout or a gateway error, or
|
|
163
|
+
when the API gives a `Retry-After` of 60 seconds or less. A refusal without one is raised at once:
|
|
164
|
+
trying again would not help.
|
|
165
|
+
|
|
166
|
+
Every estimate carries an idempotency key, the same on each attempt, so a retry counts once in your
|
|
167
|
+
quota. To make a retry of your own safe, across restarts for instance, give your own key:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
client.estimate(hts="6109.10.00.12", origin="CN", customs_value="10000", idempotency_key="po-1042")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Within 7 days, the same key with the same request gives the first response, counted once; with
|
|
174
|
+
another request, a `ConflictError`.
|
|
175
|
+
|
|
176
|
+
## Settings
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
client = TariffTrust(
|
|
180
|
+
api_key="tarifftrust_...", # or TARIFFTRUST_API_KEY
|
|
181
|
+
timeout=60.0, # seconds, at each step of an attempt: connecting, sending, each read
|
|
182
|
+
max_retries=2,
|
|
183
|
+
max_retry_wait=60.0, # the longest Retry-After waited for, in seconds
|
|
184
|
+
http_client=None, # your own httpx.Client (a proxy, your certificates): yours to close
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## The answers
|
|
189
|
+
|
|
190
|
+
An answer is an object with the fields of the [reference](https://tarifftrust.com/api/reference/objects/)
|
|
191
|
+
as attributes, typed for your editor. A field the answer leaves out reads as its default or `None`. A
|
|
192
|
+
field newer than your version of the package reads as an attribute too. `to_dict()` gives the object
|
|
193
|
+
as the API sent it, and the types are in `tarifftrust.types`.
|
|
194
|
+
|
|
195
|
+
## Operations
|
|
196
|
+
|
|
197
|
+
<!-- operations:start -->
|
|
198
|
+
| Method | Route | What it does |
|
|
199
|
+
| --- | --- | --- |
|
|
200
|
+
| `client.list_agreements()` | [`GET /v1/agreements`](https://tarifftrust.com/api/reference/#list-agreements) | List the trade agreements |
|
|
201
|
+
| `client.estimate()` | [`POST /v1/estimates`](https://tarifftrust.com/api/reference/#estimate) | Estimate one product |
|
|
202
|
+
| `client.estimate_batch()` | [`POST /v1/estimates/batch`](https://tarifftrust.com/api/reference/#estimate-batch) | Estimate up to 200 products |
|
|
203
|
+
| `client.estimate_entry()` | [`POST /v1/estimates/entry`](https://tarifftrust.com/api/reference/#estimate-entry) | Estimate a whole import declaration |
|
|
204
|
+
| `client.estimate_history()` | [`POST /v1/estimates/history`](https://tarifftrust.com/api/reference/#estimate-history) | Estimate one product at every change of the tariff |
|
|
205
|
+
| `client.list_facts()` | [`GET /v1/facts`](https://tarifftrust.com/api/reference/#list-facts) | List the questions an estimate may ask |
|
|
206
|
+
| `client.search_hts()` | [`GET /v1/hts`](https://tarifftrust.com/api/reference/#search-hts) | Search the US tariff schedule |
|
|
207
|
+
| `client.get_hts()` | [`GET /v1/hts/{code}`](https://tarifftrust.com/api/reference/#get-hts) | Read one line of the US tariff schedule |
|
|
208
|
+
| `client.list_saved_estimates()` | [`GET /v1/saved-estimates`](https://tarifftrust.com/api/reference/#list-saved-estimates) | List the saved estimates |
|
|
209
|
+
| `client.save_estimate()` | [`POST /v1/saved-estimates`](https://tarifftrust.com/api/reference/#save-estimate) | Calculate and save an estimate |
|
|
210
|
+
| `client.get_saved_estimate()` | [`GET /v1/saved-estimates/{id}`](https://tarifftrust.com/api/reference/#get-saved-estimate) | Read a saved estimate |
|
|
211
|
+
| `client.get_status()` | [`GET /v1/status`](https://tarifftrust.com/api/reference/#get-status) | Read the status and the coverage of the data |
|
|
212
|
+
<!-- operations:end -->
|
|
213
|
+
|
|
214
|
+
## Links
|
|
215
|
+
|
|
216
|
+
[API reference](https://tarifftrust.com/api/reference/) ·
|
|
217
|
+
[Integration guides](https://tarifftrust.com/guides/) ·
|
|
218
|
+
[Examples](https://github.com/tarifftrust/tarifftrust/tree/main/examples) ·
|
|
219
|
+
[Changelog](https://tarifftrust.com/changelog/) ·
|
|
220
|
+
[Data status](https://tarifftrust.com/status/)
|
|
221
|
+
|
|
222
|
+
TariffTrust provides informational estimates from official US sources. It is not a customs broker and
|
|
223
|
+
does not issue binding classifications or customs determinations.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# tarifftrust: US tariff API client for Python
|
|
2
|
+
|
|
3
|
+
The official Python client for the [TariffTrust API](https://tarifftrust.com/api/). Send an HTS code,
|
|
4
|
+
a country of origin and a value. Get every US import duty and fee that applies, from the tariff
|
|
5
|
+
schedule and Sections 301, 232, 201 and 338 to the entry fees, each with its official, dated source.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install tarifftrust
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.10 or later. Fully typed, sync and async.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Create a key in [your workspace](https://tarifftrust.com/keys/); your plan must include the API
|
|
16
|
+
([plans](https://tarifftrust.com/pricing/)). Keep it on your server.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from tarifftrust import TariffTrust
|
|
20
|
+
|
|
21
|
+
client = TariffTrust(api_key="tarifftrust_...") # or set TARIFFTRUST_API_KEY
|
|
22
|
+
|
|
23
|
+
result = client.estimate(hts="6109.10.00.12", origin="CN", customs_value="10000")
|
|
24
|
+
|
|
25
|
+
print(result.status, result.estimated_duty)
|
|
26
|
+
for component in result.components:
|
|
27
|
+
print(component.label, component.amount, component.source.url)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Read `status` first, then the amounts: decimal strings in US dollars, ready for `Decimal`.
|
|
31
|
+
|
|
32
|
+
**Unknown is never zero.** When `estimated_duty` is `None`, the duty could not be established: only
|
|
33
|
+
`known_duty_subtotal` is. See [the guide](https://tarifftrust.com/guides/#answer).
|
|
34
|
+
|
|
35
|
+
## A whole declaration
|
|
36
|
+
|
|
37
|
+
MPF and HMF are charged once on a declaration: adding up single-product estimates gives a wrong total.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
entry = client.estimate_entry(
|
|
41
|
+
transport_mode="ocean",
|
|
42
|
+
lines=[
|
|
43
|
+
{"reference": "PO-1042-1", "hts": "6109.10.00.12", "origin": "CN", "customs_value": "1000"},
|
|
44
|
+
{"reference": "PO-1042-2", "hts": "6109.10.00.12", "origin": "VN", "customs_value": "2500"},
|
|
45
|
+
],
|
|
46
|
+
)
|
|
47
|
+
print(entry.estimated_duty, entry.total_fees, entry.estimated_landed_cost)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Several products, a history, a saved estimate
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
batch = client.estimate_batch(
|
|
54
|
+
items=[
|
|
55
|
+
{"hts": "6109.10.00.12", "origin": "CN", "customs_value": "10000"},
|
|
56
|
+
{"hts": "6109.10.00.12", "origin": "VN", "customs_value": "10000"},
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
for item in batch.results:
|
|
60
|
+
print(item.index, item.result.estimated_duty if hasattr(item, "result") else item.error.code)
|
|
61
|
+
|
|
62
|
+
history = client.estimate_history(
|
|
63
|
+
hts="6109.10.00.12", origin="CN", customs_value="10000", from_="2026-10-01"
|
|
64
|
+
)
|
|
65
|
+
for point in history.points:
|
|
66
|
+
print(point.as_of, point.estimated_duty)
|
|
67
|
+
|
|
68
|
+
saved = client.save_estimate(
|
|
69
|
+
estimate={"hts": "6109.10.00.12", "origin": "CN", "customs_value": "10000"}
|
|
70
|
+
)
|
|
71
|
+
print(saved.id)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`from` is a reserved word in Python: it is `from_` here, as an argument and on an answer.
|
|
75
|
+
|
|
76
|
+
## The catalog
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
found = client.search_hts(q="cotton t-shirt", limit=5)
|
|
80
|
+
line = client.get_hts("6109.10.00.12")
|
|
81
|
+
print(line.description, line.general)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Async
|
|
85
|
+
|
|
86
|
+
`AsyncTariffTrust` has the same methods, awaited.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import asyncio
|
|
90
|
+
from tarifftrust import AsyncTariffTrust
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def main():
|
|
94
|
+
async with AsyncTariffTrust() as client:
|
|
95
|
+
return await asyncio.gather(
|
|
96
|
+
client.estimate(hts="6109.10.00.12", origin="CN", customs_value="10000"),
|
|
97
|
+
client.estimate(hts="6109.10.00.12", origin="VN", customs_value="10000"),
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Errors
|
|
102
|
+
|
|
103
|
+
Each refusal of the API is its own exception, under `APIError`.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from tarifftrust import APIError, InvalidInputError, RateLimitError
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
result = client.estimate(hts="6109.10.00.12", origin="??", customs_value="10000")
|
|
110
|
+
except InvalidInputError as error:
|
|
111
|
+
print(error.fields) # each field refused
|
|
112
|
+
except APIError as error:
|
|
113
|
+
print(error.status, error.code, error.message, error.request_id)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Exception | Status | When |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `AuthenticationError` | 401 | No valid API key |
|
|
119
|
+
| `AccessRequiredError` | 402 | No active subscription, or the quota is used up (`quota_exceeded`) |
|
|
120
|
+
| `PermissionDeniedError` | 403 | The key lacks the permission, or the plan the API |
|
|
121
|
+
| `NotFoundError` | 404 | Nothing of that name |
|
|
122
|
+
| `ConflictError` | 409 | An idempotency key sent again with another request |
|
|
123
|
+
| `PayloadTooLargeError` | 413 | Send fewer products or lines |
|
|
124
|
+
| `InvalidInputError` | 400, 422 | `fields` names each field refused; a 400 is a body that is not valid JSON |
|
|
125
|
+
| `RateLimitError` | 429 | Too many requests |
|
|
126
|
+
| `ServiceUnavailableError` | 502, 503, 504 | Unavailable for now |
|
|
127
|
+
| `APIConnectionError`, `APITimeoutError` | | The API could not be reached |
|
|
128
|
+
|
|
129
|
+
All of them inherit from `TariffTrustError`.
|
|
130
|
+
|
|
131
|
+
## Retries and idempotency
|
|
132
|
+
|
|
133
|
+
A call is tried again, twice by default, after a connection failure, a timeout or a gateway error, or
|
|
134
|
+
when the API gives a `Retry-After` of 60 seconds or less. A refusal without one is raised at once:
|
|
135
|
+
trying again would not help.
|
|
136
|
+
|
|
137
|
+
Every estimate carries an idempotency key, the same on each attempt, so a retry counts once in your
|
|
138
|
+
quota. To make a retry of your own safe, across restarts for instance, give your own key:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
client.estimate(hts="6109.10.00.12", origin="CN", customs_value="10000", idempotency_key="po-1042")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Within 7 days, the same key with the same request gives the first response, counted once; with
|
|
145
|
+
another request, a `ConflictError`.
|
|
146
|
+
|
|
147
|
+
## Settings
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
client = TariffTrust(
|
|
151
|
+
api_key="tarifftrust_...", # or TARIFFTRUST_API_KEY
|
|
152
|
+
timeout=60.0, # seconds, at each step of an attempt: connecting, sending, each read
|
|
153
|
+
max_retries=2,
|
|
154
|
+
max_retry_wait=60.0, # the longest Retry-After waited for, in seconds
|
|
155
|
+
http_client=None, # your own httpx.Client (a proxy, your certificates): yours to close
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## The answers
|
|
160
|
+
|
|
161
|
+
An answer is an object with the fields of the [reference](https://tarifftrust.com/api/reference/objects/)
|
|
162
|
+
as attributes, typed for your editor. A field the answer leaves out reads as its default or `None`. A
|
|
163
|
+
field newer than your version of the package reads as an attribute too. `to_dict()` gives the object
|
|
164
|
+
as the API sent it, and the types are in `tarifftrust.types`.
|
|
165
|
+
|
|
166
|
+
## Operations
|
|
167
|
+
|
|
168
|
+
<!-- operations:start -->
|
|
169
|
+
| Method | Route | What it does |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| `client.list_agreements()` | [`GET /v1/agreements`](https://tarifftrust.com/api/reference/#list-agreements) | List the trade agreements |
|
|
172
|
+
| `client.estimate()` | [`POST /v1/estimates`](https://tarifftrust.com/api/reference/#estimate) | Estimate one product |
|
|
173
|
+
| `client.estimate_batch()` | [`POST /v1/estimates/batch`](https://tarifftrust.com/api/reference/#estimate-batch) | Estimate up to 200 products |
|
|
174
|
+
| `client.estimate_entry()` | [`POST /v1/estimates/entry`](https://tarifftrust.com/api/reference/#estimate-entry) | Estimate a whole import declaration |
|
|
175
|
+
| `client.estimate_history()` | [`POST /v1/estimates/history`](https://tarifftrust.com/api/reference/#estimate-history) | Estimate one product at every change of the tariff |
|
|
176
|
+
| `client.list_facts()` | [`GET /v1/facts`](https://tarifftrust.com/api/reference/#list-facts) | List the questions an estimate may ask |
|
|
177
|
+
| `client.search_hts()` | [`GET /v1/hts`](https://tarifftrust.com/api/reference/#search-hts) | Search the US tariff schedule |
|
|
178
|
+
| `client.get_hts()` | [`GET /v1/hts/{code}`](https://tarifftrust.com/api/reference/#get-hts) | Read one line of the US tariff schedule |
|
|
179
|
+
| `client.list_saved_estimates()` | [`GET /v1/saved-estimates`](https://tarifftrust.com/api/reference/#list-saved-estimates) | List the saved estimates |
|
|
180
|
+
| `client.save_estimate()` | [`POST /v1/saved-estimates`](https://tarifftrust.com/api/reference/#save-estimate) | Calculate and save an estimate |
|
|
181
|
+
| `client.get_saved_estimate()` | [`GET /v1/saved-estimates/{id}`](https://tarifftrust.com/api/reference/#get-saved-estimate) | Read a saved estimate |
|
|
182
|
+
| `client.get_status()` | [`GET /v1/status`](https://tarifftrust.com/api/reference/#get-status) | Read the status and the coverage of the data |
|
|
183
|
+
<!-- operations:end -->
|
|
184
|
+
|
|
185
|
+
## Links
|
|
186
|
+
|
|
187
|
+
[API reference](https://tarifftrust.com/api/reference/) ·
|
|
188
|
+
[Integration guides](https://tarifftrust.com/guides/) ·
|
|
189
|
+
[Examples](https://github.com/tarifftrust/tarifftrust/tree/main/examples) ·
|
|
190
|
+
[Changelog](https://tarifftrust.com/changelog/) ·
|
|
191
|
+
[Data status](https://tarifftrust.com/status/)
|
|
192
|
+
|
|
193
|
+
TariffTrust provides informational estimates from official US sources. It is not a customs broker and
|
|
194
|
+
does not issue binding classifications or customs determinations.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tarifftrust"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Official Python client for the TariffTrust API: every US import duty and fee for an HTS code, an origin and a value, each with its official source."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "TariffTrust", email = "contact@tarifftrust.com" }]
|
|
10
|
+
keywords = [
|
|
11
|
+
"tariff",
|
|
12
|
+
"tariffs",
|
|
13
|
+
"hts",
|
|
14
|
+
"harmonized-tariff-schedule",
|
|
15
|
+
"customs",
|
|
16
|
+
"import-duty",
|
|
17
|
+
"landed-cost",
|
|
18
|
+
"section-301",
|
|
19
|
+
"section-232",
|
|
20
|
+
"trade-compliance",
|
|
21
|
+
"api-client",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
33
|
+
"Topic :: Office/Business :: Financial",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
35
|
+
"Typing :: Typed",
|
|
36
|
+
]
|
|
37
|
+
dependencies = ["httpx>=0.25,<1"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://tarifftrust.com/api/"
|
|
41
|
+
Documentation = "https://tarifftrust.com/api/reference/"
|
|
42
|
+
Guides = "https://tarifftrust.com/guides/"
|
|
43
|
+
Repository = "https://github.com/tarifftrust/tarifftrust"
|
|
44
|
+
Changelog = "https://tarifftrust.com/changelog/"
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = ["mypy>=1.13", "pytest>=8", "ruff>=0.8"]
|
|
48
|
+
|
|
49
|
+
[build-system]
|
|
50
|
+
requires = ["hatchling>=1.27"]
|
|
51
|
+
build-backend = "hatchling.build"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.wheel]
|
|
54
|
+
packages = ["src/tarifftrust"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
addopts = "-q"
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
line-length = 100
|
|
62
|
+
target-version = "py310"
|
|
63
|
+
extend-exclude = ["src/tarifftrust/types.py", "src/tarifftrust/_operations.py"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint]
|
|
66
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
67
|
+
|
|
68
|
+
[tool.mypy]
|
|
69
|
+
strict = true
|
|
70
|
+
files = ["src"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Official Python client for the TariffTrust API: every US import duty and fee for an HTS code,
|
|
2
|
+
an origin and a value, each with its official source. https://tarifftrust.com/api/"""
|
|
3
|
+
|
|
4
|
+
from ._client import AsyncTariffTrust, TariffTrust
|
|
5
|
+
from ._errors import (
|
|
6
|
+
AccessRequiredError,
|
|
7
|
+
APIConnectionError,
|
|
8
|
+
APIError,
|
|
9
|
+
APITimeoutError,
|
|
10
|
+
AuthenticationError,
|
|
11
|
+
ConflictError,
|
|
12
|
+
InvalidInputError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
PayloadTooLargeError,
|
|
15
|
+
PermissionDeniedError,
|
|
16
|
+
RateLimitError,
|
|
17
|
+
ServiceUnavailableError,
|
|
18
|
+
TariffTrustError,
|
|
19
|
+
)
|
|
20
|
+
from ._version import __version__
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"APIConnectionError",
|
|
24
|
+
"APIError",
|
|
25
|
+
"APITimeoutError",
|
|
26
|
+
"AccessRequiredError",
|
|
27
|
+
"AsyncTariffTrust",
|
|
28
|
+
"AuthenticationError",
|
|
29
|
+
"ConflictError",
|
|
30
|
+
"InvalidInputError",
|
|
31
|
+
"NotFoundError",
|
|
32
|
+
"PayloadTooLargeError",
|
|
33
|
+
"PermissionDeniedError",
|
|
34
|
+
"RateLimitError",
|
|
35
|
+
"ServiceUnavailableError",
|
|
36
|
+
"TariffTrust",
|
|
37
|
+
"TariffTrustError",
|
|
38
|
+
"__version__",
|
|
39
|
+
]
|