All articles
hs-codestradetariffapicustomsclassification

How to Classify Products into HS Tariff Codes with an API

Arthur Sterling

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

PlanMax description lengthRegionsMax candidates
BASIC200 charsGLOBAL only3
PRO500 charsGLOBAL, UK, US, EU5
ULTRA1,000 charsGLOBAL, UK, US, EU10
MEGA2,000 charsGLOBAL, UK, US, EU10

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.