API Reference
Everything you need to integrate the TradeFacts.io API into your application.
Get a working API response in under 60 seconds.
A plain-English overview of every query type. Click any card to jump to the full documentation and code example.
Scope note: TradeFacts returns tariff data for a given HTS or Canadian tariff code — the classification of goods to a code (applying GRI rules, determining essential character, resolving competing provisions) is performed by your broker, customs consultant, or compliance team. This API is the data layer; the classification decision stays with the human.
Drop tradefacts.py into your project. No manual header construction required.
pip install requests
from tradefacts import TradeFacts
client = TradeFacts("your-api-key")
record = client.hts("0901.11.00")
record = client.ca("0901.11.00")
results = client.search("green coffee")
changes = client.changes()https://tradefacts.io
All API endpoints are served over HTTPS. HTTP requests are redirected to HTTPS.
All endpoints (except /request-access) require a valid API key in the X-API-Key request header.
curl https://tradefacts.io/api/hts/8471.30.01.00 \
-H "X-API-Key: your_api_key_here"
Invoke-WebRequest -Uri "https://tradefacts.io/api/hts/8471.30.01.00" `
-Headers @{"X-API-Key"="your_api_key"}Missing or invalid key returns HTTP 401. Expired or cancelled subscription, or a plan attempting to access a restricted endpoint, returns HTTP 403.
All error responses return JSON with a detail field describing the error.
{ "detail": "Invalid or missing API key" }
No hard rate limits are enforced. If syncing the full dataset, batch chapter retrieval during off-peak hours.
Returns a single HTS record by its full code. The code can be passed with or without periods.
| Parameter | Type | Description |
|---|---|---|
coderequired | string | Full HTS code, e.g. 0101.30.00.00 or 0101300000 |
curl https://tradefacts.io/api/hts/8471.30.01.00 \
-H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/hts/8471.30.01.00" `
-Headers @{"X-API-Key"="your_api_key"}{
"htsno": "8471.30.01.00",
"indent": "1",
"description": "Portable automatic data processing machines, weighing not more than 10 kg, consisting of at least a central processing unit, a keyboard and a display",
"superior": null,
"units": ["No."],
"general": "Free",
"special": "",
"other": "35%",
"footnotes": [],
"quotaQuantity": null,
"additionalDuties": null
}
Keyword search across all US HTS records. Multi-word queries use AND logic — all terms must appear in the record's description or any ancestor heading in the tariff hierarchy. For example, searching "coffee" returns sub-items such as "Not decaffeinated" because their parent heading contains "coffee". Returns only classifiable tariff lines (records with a valid HTS code and duty rate or unit).
Each result includes a has_additional_duties boolean. When true, the code or its 8-digit parent has Chapter 99 cross-references — Section 301, Section 232, or IEEPA additional duties may apply depending on country of origin. Call GET /api/hts/{code} for the full chapter_99_additional_duties breakdown.
| Parameter | Type | Default | Description |
|---|---|---|---|
qrequired | string | — | Search keyword(s) |
limitoptional | integer | 50 | Max results. Maximum: 200. |
curl "https://tradefacts.io/api/search?q=copper&limit=10" \ -H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/search?q=copper&limit=10" `
-Headers @{"X-API-Key"="your_api_key"}Returns all HTS records for a given two-digit chapter.
| Parameter | Type | Description |
|---|---|---|
chapterrequired | string | Two-digit chapter, e.g. 01, 76, 99 |
curl https://tradefacts.io/api/chapter/76 \
-H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/chapter/76" `
-Headers @{"X-API-Key"="your_api_key"}Full history of detected changes to the US HTS dataset. Updated nightly at 02:00 UTC.
curl "https://tradefacts.io/api/changes" \ -H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/changes" `
-Headers @{"X-API-Key"="your_api_key"}The IEEPA Overlay provides provisional tariff rates sourced directly from Federal Register proclamations — bridging the gap between a new executive order and USITC codification, which can lag by days to weeks. Rates are manually verified by TradeFacts editorial before publication and are clearly marked PROVISIONAL. Always verify with CBP before filing.
The overlay is maintained in near-real time: the FR monitor checks for new presidential proclamations every 4 hours. When a new tariff action is detected, proposed rates are extracted and reviewed before going live. The ieepa_overlay_notice field on base HTS lookups flags when overlay entries exist that are not yet reflected in HTS footnotes.
Look up the current IEEPA provisional rate for a specific Chapter 99 code. Returns the overlay rate, source Federal Register document, verification date, and a machine-readable rate_status: "PROVISIONAL" field.
| Parameter | Type | Description |
|---|---|---|
coderequired | string | Chapter 99 HTS code, e.g. 9903.01.25 |
{
"code": "9903.01.25",
"rate_provisional": "10%",
"description": "Temporary import surcharge...",
"source_document": "FR Doc 2026-03824",
"effective_date": "2026-02-24",
"verified_date": "2026-03-31",
"verified_by": "TradeFacts editorial",
"rate_status": "PROVISIONAL",
"disclaimer": "Sourced from Federal Register..."
}Returns all known discrepancies between the IEEPA overlay and the published HTS schedule. Three discrepancy types are detected:
| Type | Meaning |
|---|---|
rate_differs | Overlay rate differs from the HTS Chapter 99 rate for the same code — the overlay reflects a more recent FR proclamation not yet codified by USITC. |
footnote_gap | Overlay code exists in the HTS but is not referenced in any base code footnote — goods subject to this rate will not have it surfaced by standard HTS lookups. The ieepa_overlay_notice field on code lookups flags this condition. |
hts_code_missing | Overlay code does not yet exist in the HTS index — a newly created Chapter 99 code ahead of USITC codification. |
The discrepancy list grows as FR proclamations are approved into the overlay. An empty result means no discrepancies are currently tracked — not that none exist. Overlay coverage expands with each approved FR document.
Full 2026 Canadian Customs Tariff sourced from CBSA — 22,461 records across all 99 chapters (11,668 primary 8-digit tariff items plus 10,793 10-digit statistical subdivisions). Every record includes the MFN rate plus all applicable preferential treatment columns from Canada's full FTA network: CUSMA/USMCA, CETA (EU), CPTPP, Canada–UK, and 15 bilateral agreements. Use the treatment parameter on search and chapter endpoints to filter by any agreement code.
Canada endpoints mirror the US HTS endpoints and use the same X-API-Key authentication. The base path is /api/ca/ instead of /api/hts/.
Note: Retaliatory surtax overlay (separate CBSA notices on top of MFN rates) is on the roadmap and not yet included.
Returns a single Canadian tariff record by its full tariff code.
| Parameter | Type | Description |
|---|---|---|
coderequired | string | Full CA tariff code, e.g. 0101.21.00 |
curl https://tradefacts.io/api/ca/0101.21.00 \
-H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/ca/0101.21.00" `
-Headers @{"X-API-Key"="your_api_key"}Keyword search across all Canadian Customs Tariff records. Multi-word queries use AND logic — all terms must appear in the record's description or any ancestor heading in the tariff hierarchy. Deduplicates statistical suffix variants â where both an 8-digit tariff item and its 10-digit statistical subdivision exist, only the 8-digit record is returned. Both forms can be retrieved directly via /api/ca/{code}.
| Parameter | Type | Default | Description |
|---|---|---|---|
qrequired | string | — | Search keyword(s) |
limitoptional | integer | 50 | Max results. Maximum: 200. |
treatmentoptional | string | — | Filter by preferential treatment code, e.g. CPTPT, CEUT, KRT. See treatment codes table. |
# Filter aluminum records with CPTPP preferential rate curl "https://tradefacts.io/api/ca/search?q=aluminum&treatment=CPTPT" \ -H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/ca/search?q=aluminum&limit=10" `
-Headers @{"X-API-Key"="your_api_key"}Returns all Canadian tariff records for a given two-digit chapter.
| Parameter | Type | Description |
|---|---|---|
chapterrequired | string | Two-digit chapter, e.g. 84 |
treatmentoptional | string | Filter by preferential treatment code, e.g. CEUT, CPTPT, UST. See treatment codes table. |
curl https://tradefacts.io/api/ca/chapter/84 \
-H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/ca/chapter/84" `
-Headers @{"X-API-Key"="your_api_key"}Filter Canadian tariff records by UST (United States Tariff) rate — the CUSMA/USMCA preferential rate applied to US-origin goods.
| Parameter | Type | Default | Description |
|---|---|---|---|
rateoptional | string | — | UST rate value, e.g. Free, 6.5% |
limitoptional | integer | 100 | Max results. Maximum: 500. |
# Get all tariff lines where US goods enter Canada duty-free curl "https://tradefacts.io/api/ca/ust?rate=Free" \ -H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/ca/ust?rate=Free" `
-Headers @{"X-API-Key"="your_api_key"}Full history of detected changes to the Canadian Customs Tariff dataset. Updated nightly at 02:30 UTC.
curl "https://tradefacts.io/api/ca/changelog" \ -H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/ca/changelog" `
-Headers @{"X-API-Key"="your_api_key"}Full 2026 Mexico TIGIE (Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación) sourced from SNICE (Secretaría de Economía) — 8,183 fracciones arancelarias across 97 chapters. Includes the IGI (general import rate / MFN), export rate, and unit of measure for every fraction. Updated when SNICE publishes a new bulk release. All descriptions are in Spanish as published by the official authority.
Mexico endpoints mirror the US and Canada endpoints and use the same X-API-Key authentication. The base path is /api/mx/.
Look up a single Mexico TIGIE fraccion arancelaria. Accepts with or without dots — 0101.21.01 and 01012101 both work.
curl "https://tradefacts.io/api/mx/7201.10.01" -H "X-API-Key: your_api_key"
Invoke-WebRequest -Uri "https://tradefacts.io/api/mx/7201.10.01" `
-Headers @{"X-API-Key"="your_api_key"}{
"fraccion": "7201.10.01",
"chapter": "72",
"description": "Fundición en bruto sin alear con un contenido de fósforo inferior al 0,5% en peso",
"unit": "Kg",
"igi": "35",
"ige": "Ex."
}
Keyword search across Mexico TIGIE descriptions. Multi-word AND logic. Descriptions are in Spanish as published by SNICE.
| Parameter | Type | Description |
|---|---|---|
q | string | Search query. Multi-word terms use AND logic. |
limit | integer | Max results (default 50, max 200). |
curl "https://tradefacts.io/api/mx/search?q=acero+laminado" -H "X-API-Key: your_api_key"
All Mexico TIGIE records for a given chapter (01–98). Returns the full array of fracciones for that chapter.
curl "https://tradefacts.io/api/mx/chapter/72" -H "X-API-Key: your_api_key"
History of detected changes to the Mexico TIGIE dataset. Updated when SNICE publishes a new bulk release (checked nightly at 03:00 UTC). Returns an empty array when no changes have been detected yet.
curl "https://tradefacts.io/api/mx/changelog" -H "X-API-Key: your_api_key"
Webhooks eliminate polling. Instead of querying /api/changes each night, register an HTTPS endpoint once. TradeFacts POSTs a signed payload automatically whenever the nightly update detects a rate change — US HTS or Canadian Customs Tariff.
Delivery fires after each nightly ingest: ~02:00 UTC for US changes, ~02:30 UTC for CA changes. No changes detected = no POST sent.
Every POST is signed with HMAC-SHA256 using the secret you set at registration — the same pattern used by Stripe and GitHub webhooks.
Register or replace your webhook endpoint. Supply any secret you choose (16+ characters). Calling this again overwrites the previous URL and secret.
| Field | Type | Required | Notes |
|---|---|---|---|
url | string | Yes | HTTPS only |
secret | string | Yes | Min 16 chars. You choose it. Store it securely. |
curl -X POST "https://tradefacts.io/api/webhook/register" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"url":"https://your-app.com/hooks/tradefacts","secret":"your-secret-min-16-chars"}'Response on success:
{
"status": "registered",
"url": "https://your-app.com/hooks/tradefacts",
"note": "Fires nightly 02:00-02:30 UTC when changes detected."
}Every POST body is JSON. The top-level envelope:
| Field | Type | Description |
|---|---|---|
source | string | "US" or "CA" |
event | string | Always "tariff_change" |
timestamp | string | ISO 8601 UTC delivery time |
change_count | integer | Number of changed records in this payload |
changes | array | Array of change objects (see below) |
Each object in changes has a type field:
| type | Fields present |
|---|---|
modified | code, description, changed_fields — object with {field: {old, new}} pairs for every changed field |
added | code, description, plus general/units (US) or mfn/ust (CA) |
removed | code, description |
Example payload (US rate modification):
{
"source": "US",
"event": "tariff_change",
"timestamp": "2026-03-19T02:14:37Z",
"change_count": 1,
"changes": [
{
"type": "modified",
"code": "7606.12.30",
"description": "Of aluminum alloys, not alloyed",
"changed_fields": {
"additionalDuties": {
"old": "25% (Section 232)",
"new": "25% (Section 232) + 10% (IEEPA)"
}
}
}
]
}Every POST includes an X-TradeFacts-Signature header. The value is sha256=<hex> — HMAC-SHA256 of the raw request body using your secret.
Always verify before processing. Use a constant-time comparison to prevent timing attacks.
import hmac, hashlib
def verify_tradefacts_signature(raw_body: bytes, secret: str, header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)
# Flask example
from flask import request, abort
@app.route("/hooks/tradefacts", methods=["POST"])
def handle_webhook():
sig = request.headers.get("X-TradeFacts-Signature", "")
if not verify_tradefacts_signature(request.get_data(), SECRET, sig):
abort(400, "Invalid signature")
payload = request.get_json()
for change in payload["changes"]:
print(change["code"], change.get("changed_fields", {}))
return "", 200const crypto = require("crypto");
function verifySignature(rawBody, secret, header) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(header)
);
}
// Express example
app.post("/hooks/tradefacts", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-tradefacts-signature"] || "";
if (!verifySignature(req.body, process.env.TRADEFACTS_SECRET, sig)) {
return res.status(400).send("Invalid signature");
}
const payload = JSON.parse(req.body);
payload.changes.forEach(c => console.log(c.code, c.changed_fields));
res.sendStatus(200);
});Retry behaviour: If your endpoint does not respond with HTTP 2xx, TradeFacts retries 3 times with exponential backoff (5s, 15s, 45s). After all attempts fail, Rick receives an alert email.
Idempotency: Each nightly run generates one POST per source (US, CA). There is no deduplication guarantee across retries — your endpoint should be idempotent (safe to receive the same payload twice).
All US HTS endpoints return records conforming to this schema.