Trade Classification Engine API
v1.0Assign HS tariff codes to products with the AI-powered Trade Classification Engine.
Overview
The Trade Classification Engine API is a decision engine, not a simple lookup table. Send a product description and receive ranked HS code candidates with confidence scores, reasoning, and follow-up questions when the input is ambiguous.
🎯 Progressive
Narrows down codes across multiple calls with follow-up questions
🌍 Multi-Region
Covers GLOBAL (HS6), UK, US, and EU tariff schedules
📊 Explainable
Every result includes confidence scores and reasoning
💡 Perfect For
- • Customs brokers and freight forwarders
- • Cross-border e-commerce platforms
- • ERP and trade compliance systems
- • Supply chain and logistics software
Quick Start
Base URL
https://api.cparse.com/trade/v1Authentication
X-API-Key: YOUR_API_KEYGet your API key from the dashboard. New accounts include free credit, no credit card required.
Quick Example
Classify a product description:
cURL
curl --request POST \
--url https://api.cparse.com/trade/v1/classify \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--data '{
"description": "blue cotton t-shirt",
"region": "GLOBAL"
}'Refining with Context
If the initial response includes follow-up questions, re-send with answers:
curl --request POST \
--url https://api.cparse.com/trade/v1/classify \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--data '{
"description": "men'\''s knitted cotton t-shirt 100%",
"region": "GLOBAL",
"context": {
"construction": "knit",
"gender": "men",
"material": "100% cotton"
}
}'Endpoints
/classifyClassify a product description into HS tariff codes.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| description | string | Yes | Product description (3–2000 chars) |
| region | string | No | Target tariff schedule. One of GLOBAL, UK, US, EU. Defaults to GLOBAL. |
| context | object | No | Answers to previous follow-up questions. Each key is the attribute from a follow_up_questions item; each value is the chosen answer. Omit on the first call. |
| heading_hint | string | No | 4-digit HS heading to lock the search to (e.g. 6109). Pass the top_heading value from the previous response. This prevents the classifier from drifting into unrelated product categories across rounds. |
The context field
When the response contains follow_up_questions, collect answers from the user and send them back in the next request. The key is the attribute name from the question; the value is the user's answer (one of options, or free text when options is null).
// Response contains:
"top_heading": "6109",
"follow_up_questions": [
{
"attribute": "gender_age",
"question": "Who is this garment intended for?",
"options": ["men", "women", "boys", "girls", "unisex"]
},
{
"attribute": "material",
"question": "What is the primary material or composition?",
"options": ["cotton", "man-made fibres", "wool", "silk"]
}
]
// Next request — attribute becomes context key, top_heading becomes heading_hint:
{
"description": "blue cotton t-shirt",
"region": "GLOBAL",
"heading_hint": "6109",
"context": {
"gender_age": "men",
"material": "cotton"
}
}Accumulate answers across rounds — include all previous answers plus any new ones in each subsequent request.
/regionsList supported classification regions. No authentication required.
Response Fields
| Field | Type | Description |
|---|---|---|
| status | string | final — confident result, no further input needed. needs_more_info — ambiguous, check follow_up_questions. |
| candidates | array | Ranked list of HS code candidates, best match first |
| candidates[].code | string | HS or tariff code |
| candidates[].description | string | Official tariff description |
| candidates[].confidence | number | Score from 0.0 to 1.0. Above 0.85 is considered confident. |
| candidates[].duty_rate | string | null | Applicable duty rate (available for UK, US, EU regions) |
| candidates[].reasoning | string | null | Why this code was selected |
| follow_up_questions | array | Empty when status is final. Otherwise contains questions whose answers would narrow the classification. |
| follow_up_questions[].attribute | string | The attribute name. Use this as the key in context in the next request. |
| follow_up_questions[].question | string | Human-readable question to present to the user |
| follow_up_questions[].options | string[] | null | Suggested answers. Null means free text is expected. |
| region | string | Region used for this classification |
| top_heading | string | null | The dominant 4-digit HS heading among the top candidates. Pass this back as heading_hint in subsequent requests to lock the search space and prevent category drift. |
| explanation | string | null | Overall reasoning summary. Populated when status is final. |
Multi-turn flow
When you receive needs_more_info, present the questions to your user, then send a new request with their answers in context. Keep the same description and region across rounds.
// Round 1 — initial request
POST /classify
{
"description": "blue cotton t-shirt",
"region": "US"
}
// Round 1 — response (ambiguous)
{
"status": "needs_more_info",
"top_heading": "6109", // <-- save this
"candidates": [ ... ],
"follow_up_questions": [
{
"attribute": "gender_age",
"question": "Who is this garment intended for?",
"options": ["men", "women", "boys", "girls", "unisex"]
},
{
"attribute": "material",
"question": "What is the primary material or composition?",
"options": ["cotton", "man-made fibres", "wool", "silk"]
}
]
}
// Round 2 — pass top_heading back as heading_hint to lock the search space
POST /classify
{
"description": "blue cotton t-shirt",
"region": "US",
"heading_hint": "6109", // <-- locks search to heading 6109
"context": {
"gender_age": "men",
"material": "cotton"
}
}
// Round 2 — response (final, stays within heading 6109)
{
"status": "final",
"top_heading": "6109",
"candidates": [
{
"code": "6109100012",
"description": "Men's T-shirts, of cotton",
"confidence": 0.94,
"heading": "6109",
"duty_rate": "16.5%",
"reasoning": "Matches men's standard cotton T-shirt under heading 6109"
}
],
"follow_up_questions": [],
"explanation": "Classified as men's cotton T-Shirt under HTS 6109.10"
}Example Response (Ambiguous)
{
"status": "needs_more_info",
"candidates": [
{
"code": "610910",
"description": "T-shirts, knitted, of cotton",
"confidence": 0.78,
"heading": "6109",
"duty_rate": "12%",
"reasoning": "Strong match for cotton t-shirt under chapter 61"
},
{
"code": "610990",
"description": "T-shirts, other materials",
"confidence": 0.42,
"reasoning": "Possible if material is not 100% cotton"
}
],
"follow_up_questions": [
{
"attribute": "construction",
"question": "Is the garment knitted or woven?",
"options": ["knitted", "woven"]
},
{
"attribute": "gender",
"question": "Is it for men, women, or children?",
"options": ["men", "women", "boys", "girls", "unisex"]
}
],
"region": "GLOBAL",
"explanation": null
}Example Response (Final)
{
"status": "final",
"candidates": [
{
"code": "610910",
"description": "T-shirts, knitted, of cotton",
"confidence": 0.94,
"heading": "6109",
"duty_rate": "12%",
"reasoning": "Matches knitted cotton garment under chapter 61"
}
],
"follow_up_questions": [],
"region": "GLOBAL",
"explanation": "Classified as knitted cotton t-shirt under HS heading 6109"
}Plans
| Feature | Basic | Pro | Ultra | Mega |
|---|---|---|---|---|
| Max description length | 200 chars | 500 chars | 1,000 chars | 2,000 chars |
| Regions | GLOBAL | All 4 | All 4 | All 4 |
| Max candidates | 3 | 5 | 10 | 10 |
API key users always get Mega plan limits. The balance is the only limiting factor.
Error Handling
| Status | Meaning |
|---|---|
| 400 | Invalid region or malformed request body |
| 401 | Missing or invalid API key |
| 402 | Insufficient balance |
| 413 | Description too long or region not allowed for your plan |
| 422 | Validation error (missing required fields, etc.) |
| 503 | Tariff data not loaded (temporary) |
Code Examples
Python
import requests
url = "https://api.cparse.com/trade/v1/classify"
headers = {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
}
# Round 1 — initial classification
response = requests.post(url, headers=headers, json={
"description": "blue cotton t-shirt",
"region": "US",
})
data = response.json()
if data["status"] == "final":
print(data["candidates"][0]["code"])
else:
# Save top_heading to lock the search space in the next round
heading_hint = data.get("top_heading")
# Collect answers to follow-up questions
# attribute field = context key, user answer = context value
answers = {}
for q in data["follow_up_questions"]:
answers[q["attribute"]] = q["options"][0] if q["options"] else "unknown"
response = requests.post(url, headers=headers, json={
"description": "blue cotton t-shirt",
"region": "US",
"heading_hint": heading_hint, # prevents category drift
"context": answers,
})
refined = response.json()
print(refined["status"])
print(refined["candidates"][0]["code"])
JavaScript / Node.js
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: "blue cotton t-shirt",
region: "GLOBAL",
}),
});
const data = await response.json();
console.log(data.status);
console.log(data.candidates);
if (data.follow_up_questions.length > 0) {
console.log("Follow-up questions:", data.follow_up_questions);
}Tips
Be specific
Include material, construction method, intended use, and gender when possible. "Men's 100% cotton knitted t-shirt" will give better results than "t-shirt".
Use the follow-up flow
When the API returns needs_more_info, use the follow-up questions to collect answers and send them back in the context field for a more confident result.
Choose the right region
Use GLOBAL for generic 6-digit HS codes. Use country-specific regions (UK, US, EU) when you need full tariff codes with duty rates.