How to Classify Products into HS Tariff Codes with an API
Arthur Sterling
Lead Developer Advocate, Parse
How to Classify Products into HS Tariff Codes with an API
Assigning the correct HS (Harmonized System) tariff code to a product is one of the more error-prone tasks in international trade. The tariff schedule has thousands of headings, and a vague description like "blue cotton t-shirt" could map to several different codes depending on construction method, gender targeting, and knit type. Getting it wrong means incorrect duty calculations, customs delays, and compliance risk.
The Parse Trade Classification Engine API uses AI to narrow down candidates from a full tariff database and return ranked HS codes with confidence scores and reasoning. When the description is ambiguous, the API asks specific follow-up questions so you can refine the result without guessing.
What You Get Back
A single call to /classify returns:
- •
status: Either"final"(confident result) or"needs_more_info"(ambiguous input) - •
candidates: Ranked list of HS code matches, each with a confidence score, official description, duty rate, and reasoning - •
follow_up_questions: Structured questions to resolve ambiguity, with suggested answer options - •
top_heading: The dominant 4-digit HS heading, used to lock subsequent requests to the right search space - •
explanation: A plain-language summary of why the top code was selected (when status is"final")
Supported regions: GLOBAL (HS6), UK, US, and EU.
Getting Started
Sign up at cparse.com/dashboard and copy your API key. New accounts include free credit with no credit card required.
The base URL is https://api.cparse.com/trade/v1.
Python Example: Single-Pass Classification
import requests
url = "https://api.cparse.com/trade/v1/classify"
headers = {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
}
payload = {
"description": "men's knitted cotton t-shirt 100%",
"region": "US",
}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
print(f"Status: {data['status']}")
for candidate in data["candidates"]:
print(f"{candidate['code']} {candidate['confidence']:.0%} {candidate['description']}")
if candidate.get("duty_rate"):
print(f" Duty rate: {candidate['duty_rate']}")
if candidate.get("reasoning"):
print(f" Reasoning: {candidate['reasoning']}")
Handling Ambiguous Descriptions
When status is "needs_more_info", the response includes follow_up_questions. Each question has an attribute name, a human-readable question, and optional answer options. Collect those answers and send them back in the context field along with heading_hint to lock the search space.
import requests
url = "https://api.cparse.com/trade/v1/classify"
headers = {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
}
# First call — description is ambiguous
response = requests.post(url, json={"description": "blue cotton t-shirt", "region": "GLOBAL"}, headers=headers)
data = response.json()
if data["status"] == "needs_more_info":
print("Follow-up questions:")
for q in data["follow_up_questions"]:
print(f" [{q['attribute']}] {q['question']}")
if q.get("options"):
print(f" Options: {', '.join(q['options'])}")
# Second call — supply answers and lock the heading
refined_response = requests.post(url, json={
"description": "blue cotton t-shirt",
"region": "GLOBAL",
"heading_hint": data["top_heading"],
"context": {
"construction": "knit",
"gender": "men",
"material": "100% cotton",
},
}, headers=headers)
refined = refined_response.json()
top = refined["candidates"][0]
print(f"\nFinal code: {top['code']} ({top['confidence']:.0%} confidence)")
print(f"Explanation: {refined.get('explanation')}")
JavaScript Example
const response = await fetch('https://api.cparse.com/trade/v1/classify', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
description: "men's knitted cotton t-shirt 100%",
region: 'US',
}),
});
const data = await response.json();
console.log(`Status: ${data.status}`);
for (const candidate of data.candidates) {
console.log(
`${candidate.code} ${(candidate.confidence * 100).toFixed(0)}% ${candidate.description}`
);
}
if (data.status === 'needs_more_info') {
for (const q of data.follow_up_questions) {
console.log(`[${q.attribute}] ${q.question}`);
}
}
Understanding Confidence Scores
Each candidate has a confidence value between 0.0 and 1.0. A score above 0.85 on the top candidate with a "final" status is a strong signal. For automated pipelines, you can use confidence as a routing condition: auto-accept high-confidence results and queue low-confidence ones for human review.
TOP_THRESHOLD = 0.85
top = data["candidates"][0]
if data["status"] == "final" and top["confidence"] >= TOP_THRESHOLD:
accepted_code = top["code"]
else:
# Route to human review or send a follow-up request.
pass
Plans and Limits
| Plan | Max description length | Regions | Max candidates |
|---|---|---|---|
| BASIC | 200 chars | GLOBAL only | 3 |
| PRO | 500 chars | GLOBAL, UK, US, EU | 5 |
| ULTRA | 1,000 chars | GLOBAL, UK, US, EU | 10 |
| MEGA | 2,000 chars | GLOBAL, UK, US, EU | 10 |
API-key users (via the dashboard) always receive MEGA limits.
Next Steps
See the full endpoint reference, request schema, and field descriptions in the Trade Classification Engine documentation. Get your API key from cparse.com/dashboard.
Arthur Sterling is the Lead Developer Advocate at Parse.