hscode
EU customs tariff (TARIC) API client for Python: classify products into HS / CN / TARIC codes and get import duties.
A small client for the hscode.info HS code API. It covers HS code classification, HS code lookup and import duty for the EU Combined Nomenclature.
- German, English and French descriptions. Queries in any language: Zolltarifnummer, nomenclature combinée, tarif douanier.
- Zero dependencies. It uses the standard library only. Python 3.9 or newer.
- Typed. Responses are
TypedDicts and the package shipspy.typed. - Built for data teams: classify a pandas column or a product export in chunks of 25.
Install
pip install hscode
Get a key on your hscode.info account. The free plan has 30 AI units and 100 lookups a day, with no credit card. Paid plans start at $19 a month. See pricing.
Quick start
from hscode import Client
client = Client(api_key="...") # or set HSCODE_KEY
result = client.classify("cotton t-shirt", mode="accurate")
top = result["results"][0]
print(top["goods_code"], top["description"])
The CLI ships with the npm package, not with this one: npx @hscode/sdk classify "cotton t-shirt". See the TypeScript SDK and CLI.
Reference
Client(api_key=None, base_url="https://hscode.info", timeout=60, opener=None)
| Argument | Default |
|---|---|
api_key |
the HSCODE_KEY environment variable |
base_url |
https://hscode.info |
timeout |
60 seconds |
opener |
urllib.request.urlopen. Pass your own callable (request, timeout=...) to fake the network in tests. |
lang is "de", "en" or "fr" and defaults to "en". mode is "fast" (Quick, 1 AI unit) or "accurate" (Precise, 5 units, adds confidence and reasoning). When you omit year, the client looks up the latest snapshot once and reuses it.
Goods codes may contain spaces or dots. The client strips them and checks for 10 digits before it sends a request.
classify
res = client.classify("frozen shrimp, peeled", mode="accurate", lang="de")
print(res["results"][0]["goods_code"], res["question"], res["disclaimer"])
POST /api/v1/classify. Each result has goods_code, description, path, score, confidence, reasoning and duties.
classify_batch
res = client.classify_batch(["cotton t-shirt", {"id": "SKU-2", "text": "steel nails"}])
for item in res["items"]:
print(item["id"], item["results"][0]["goods_code"] if item["results"] else item["error"])
POST /api/v1/classify/batch. One to 25 items per call. An item is a string or a dict with text and an optional id. A string gets its list position as id. An item that fails has error set.
lookup
detail = client.lookup("6109 10 00 10", year="2025", lang="fr")
GET /api/v1/{lang}/{year}/codes/{code}. Returns the description, hierarchy, duties and signals such as quotas and restrictions. Pass history=True to add the duties of every yearly snapshot.
history
detail = client.history("6109100010")
for snapshot in detail["history"]:
print(snapshot["year"], len(snapshot["duties"]))
Same as lookup with history=True. Use it for duty history by year.
search
hits = client.search("t-shirt")["items"]
GET /api/v1/{lang}/{year}/search. Matches codes by words.
duties
duties = client.duties("6109100010")["items"]
GET /api/v1/{year}/duties/{code}. Import duty per origin, with legal base and measure type. Use it as the input of a customs duty calculator.
chapters and chapter
chapters = client.chapters()["items"]
rows = client.chapter("61")["items"]
The nomenclature chapters, and the rows of one 2-digit chapter.
snapshots
snapshots = client.snapshots()
print(snapshots["latest"])
The yearly TARIC snapshots from 2016 to 2026.
Classify a pandas column
classify_batch takes 25 items at most. Split the column in chunks of 25.
import pandas as pd
from hscode import Client
client = Client()
df = pd.read_csv("products.csv")
texts = df["description"].astype(str).tolist()
codes = []
for start in range(0, len(texts), 25):
chunk = texts[start : start + 25]
for item in client.classify_batch(chunk)["items"]:
top = item["results"][0] if item["results"] else None
codes.append(top["goods_code"] if top else None)
df["hs_code"] = codes
df.to_csv("classified.csv", index=False)
pandas is only used in this example. The package does not depend on it.
Errors
Every failure raises HscodeError.
from hscode import Client, HscodeError
client = Client()
try:
client.lookup("1234")
except HscodeError as e:
print(e.status, e.code, e.message)
| Field | Meaning |
|---|---|
status |
HTTP status (400, 401, 404, 429, 503). 0 for errors raised before a response. |
code |
The API error string, or invalid_request, missing_api_key and network_error for local errors. |
message |
A readable message. str(e) returns it. |
On HTTP 429 your plan quota is used up. See pricing.
Accuracy
On 1,000 real EU Binding Tariff Information (BTI, also called EBTI) rulings sampled from a pool of about 120,000, covering 94 chapters, the first suggestion is right at 2 digits (chapter) in 91 %, at 4 digits (heading) in 87.9 % and at 6 digits (subheading) in 84.7 %. As of October 2026, Precise mode. Details: accuracy.
Advisory only. Only a BTI from customs is legally binding.
For AI agents
The same tools are available as an MCP server at hscode.info/mcp: classify, lookup_code, search_codes and duty_history. Setup is on the developers page. See also llms.txt.
Links
- hscode.info: HS code lookup and classification
- Developers
- API reference and OpenAPI spec
- Pricing
- HS codes by product
- TypeScript SDK and CLI on npm
- Source on GitHub
MIT license.
Metadata
Release files for hscode 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| hscode-0.1.1.tar.gz | 9.9 kB | Without provenance | Details for hscode-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| hscode-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for hscode-0.1.1-py3-none-any.whl |
Total release size: 19.2 kB
Release files / hscode-0.1.1.tar.gz
| Download URL | hscode-0.1.1.tar.gz |
|---|---|
| Size | 9.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2b8f0e45f3155ae2036e3a203b8c723eaeccf63315fd5242f93a0a51a44c6785
|
|
BLAKE2b-256 checksum How to use checksums |
490d3d38d67a53121ce43fc47a99565d9223cac4ebd72bfa20db1d1f18ebf61f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / hscode-0.1.1-py3-none-any.whl
| Download URL | hscode-0.1.1-py3-none-any.whl |
|---|---|
| Size | 9.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b35079b49a4edd6d378050acfa46bab5f9977fc2ddff53bc16d2cbf9189b3aa6
|
|
BLAKE2b-256 checksum How to use checksums |
a2038841a8ce317f6d7f3688a62287a9f74d61c36c458309a7c86c3f2f52765f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for hscode 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.