HTS Classification API: Developer Integration Guide
One POST request. Describe a product in plain English, get 10-digit HTS codes with duty rates, Section 301/232 tariffs, CBP ruling evidence, and FTA rates. JSON in, JSON out.
Quick start
1. Get an API key
Buy a credit pack. Each /classify call costs 1 credit. All other endpoints are free.
2. Make your first call
# Classify a product curl -X POST https://htsapi.dev/v1/classify \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{ "description": "wireless bluetooth headphones with noise cancellation", "country_of_origin": "China" }'
3. Parse the response
{
"confident_to": "8518.30",
"candidates": [
{
"hts_code": "8518.30.20.00",
"description": "Headphones and earphones...",
"general_duty": "Free",
"census_duties": {
"country": "CHINA",
"period": "2026-01",
"total_effective_rate_pct": 16.9,
"total_import_value_usd": 61910940,
"total_duty_collected_usd": 10463040,
"breakdown": [
{ "rp_code": "00", "rp_label": "MFN base rate", "effective_rate_pct": 0.0 },
{ "rp_code": "69", "rp_label": "Additional tariffs (Section 301/232/reciprocal)", "effective_rate_pct": 25.9 }
],
"aggregate_import_value_usd": 443182895
},
"confidence": "high",
"rationale": "This code covers headphones...",
"rulings": [
{
"ruling_number": "N302512",
"subject": "The tariff classification of audio headphones from China",
"source_url": "https://rulings.cbp.gov/ruling/N302512"
}
],
"legal_notes": "--- SECTION XVI ---\n Notes\n1 This section does not cover..."
}
],
"clarification": null,
"model_used": "gemini-2.5-flash-lite"
}
Code examples
Python
import requests resp = requests.post( "https://htsapi.dev/v1/classify", headers={"X-API-Key": "YOUR_API_KEY"}, json={ "description": "stainless steel water bottle 500ml insulated", "country_of_origin": "China" } ) data = resp.json() # Top candidate top = data["candidates"][0] print(f"HTS: {top['hts_code']}") # 7323.93.00.50 print(f"MFN duty: {top['general_duty']}") # 2% print(f"Confident to: {data['confident_to']}") # Census effective duty rate (ground truth from CBP) census = top["census_duties"] if census: print(f"Effective rate: {census['total_effective_rate_pct']}% from {census['country']}") print(f"Import volume: ${census['total_import_value_usd']:,}") for b in census["breakdown"]: print(f" {b['rp_label']}: {b['effective_rate_pct']}%")
Node.js
const resp = await fetch("https://htsapi.dev/v1/classify", { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": "YOUR_API_KEY" }, body: JSON.stringify({ description: "ceramic coffee mug handmade", country_of_origin: "Japan" }) }); const data = await resp.json(); const top = data.candidates[0]; console.log(top.hts_code); // 6912.00.44.00 console.log(top.general_duty); // 10% console.log(top.census_duties); // {total_effective_rate_pct: 3.5, ...} console.log(data.confident_to); // 6912.00
Key concepts
confident_to — how deep to trust
The deepest HTS prefix that all high/medium-confidence candidates agree on. This tells you where classification certainty stops.
| Value | Meaning | What you can do |
|---|---|---|
6109.10.00.12 | Full 10-digit agreement | File with high confidence |
6109.10.00 | 8-digit agreement | Duty rate is known, statistical suffix varies |
6109.10 | 6-digit agreement | Product type is clear, specifics needed |
6109 | 4-digit (heading) | Broad category only |
null | No agreement | Description is too vague, add detail |
census_duties — ground truth duty rates
Effective duty rates from the US Census Bureau — what CBP actually collected at the port. Includes a breakdown by rate provision: MFN base rate, FTA/special programs, additional tariffs (Section 301/232/reciprocal), and exclusions. This is the real-world duty burden, not a theoretical calculation. Requires country_of_origin for country-specific rates; without it, returns aggregate across all countries.
legal_notes — official classification guidance
Section, Chapter, and Heading notes from the official tariff schedule — the legal reasoning behind why a product belongs in a specific classification. These are the General Rules of Interpretation (GRI) notes that customs brokers use for classification decisions.
clarification — when the API needs more info
When candidates diverge, the API returns a question to narrow it down. If the top candidate is high confidence, clarification is null. Use this in conversational flows — ask the user, re-classify with more detail.
All endpoints
| Method | Endpoint | Cost | Description |
|---|---|---|---|
POST | /v1/classify | 1 credit | AI classification with full enrichment |
GET | /v1/hts/search?q= | Free | Search HTS codes by keyword or prefix |
GET | /v1/hts/{code} | Free | Single code detail with hierarchy |
GET | /v1/rulings/search?q= | Free | Search 134K CBP rulings |
GET | /v1/rulings/{id} | Free | Full ruling text |
GET | /v1/billing/balance | Free | Check remaining credits |
GET | /v1/status | Free | System health + data freshness |
Only /classify costs credits. Everything else is free and unlimited (within rate limits).
Request fields
| Field | Type | Required | Notes |
|---|---|---|---|
description | string | Yes | 3-2000 chars. More detail = better results. |
material | string | No | Primary material (e.g., "cotton", "stainless steel") |
category | string | No | Product category hint |
country_of_origin | string | No | Required for Section 301/232 duties and FTA filtering |
country_of_origin. Without it, additional duties won't appear and confidence tends to be lower. Country names work (e.g., "China", "Vietnam") — no need for ISO codes.
Tips for better results
- Be specific. "men's 100% cotton knit t-shirt short sleeve" beats "t-shirt".
- Include material. HTS classification often hinges on material composition.
- Mention function/end-use. "brake disc rotor for passenger car" beats "metal disc".
- Use the clarification. If the API asks a question, answer it and re-classify with the added detail.
- Check
confident_to. If it's only 4 digits, the description is too vague — add detail.
Error handling
| Status | Code | Meaning |
|---|---|---|
| 401 | MISSING_API_KEY | No X-API-Key header |
| 401 | INVALID_API_KEY | Key not found or revoked |
| 402 | NO_CREDITS | Credits exhausted — buy more |
| 403 | ACCOUNT_INACTIVE | Account disabled |
| 422 | Validation error | Description too short/long |
| 503 | CLASSIFY_UNAVAILABLE | Model or DB not ready |
Rate limits
Default: 60 requests per minute per API key. If you need higher limits, contact us.
Data freshness
Check GET /v1/status for per-source freshness. HTS data updates on USITC revision (~monthly). CBP rulings update daily. FTA rates update weekly.