Classification API Documentation
Integrate HS code classification into your applications with the Import8 REST API. Classify products using image, text, or both.
https://import8.nl/api
Authentication
All API requests (except /health) require authentication via an API key. Generate API keys in your dashboard.
Pass your API key via the Authorization header using the Bearer scheme:
curl -H "Authorization: Bearer c8_your_api_key" \
https://import8.nl/api/v1/me
import requests
response = requests.get(
"https://import8.nl/api/v1/me",
headers={"Authorization": "Bearer c8_your_api_key"}
)
print(response.json())
const response = await fetch('https://import8.nl/api/v1/me', {
headers: { 'Authorization': 'Bearer c8_your_api_key' }
});
const data = await response.json();
Usage Tracking
All classification endpoints include a usage object in the response, so you can track your remaining credits without a separate API call.
"usage": {
"credits_remaining": 47
}
| Field | Type | Description |
|---|---|---|
| credits_remaining | integer | Number of credits remaining across all active balances |
/v1/classify
Submit a product for HS code classification. The classification is processed asynchronously -- you receive a 202 Accepted response with a classification ID. Poll for results or configure a webhook.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| image_url | string | optional | URL of a product image (up to 5 images supported) |
| image_base64 | string | optional | Base64-encoded product image |
| description | string | optional | Product text description |
| origin_country | string | optional | 2-letter ISO country code of product origin |
| destination_country | string | optional | 2-letter ISO country code (default: NL) |
| goods_value | number | optional | Unit price of the goods (FOB). When provided, landed cost is calculated automatically after classification. |
| quantity | integer | optional | Number of units (default: 1) |
| shipping_cost | number | optional | Shipping / freight cost (default: 0) |
| insurance_cost | number | optional | Insurance cost (default: 0) |
| currency | string | optional | 3-letter currency code (default: EUR). Supported: EUR, USD, GBP, CNY. |
Note: At least one of image_url, image_base64, or description is required.
Landed cost: When goods_value is provided, the classification result will include a landed_cost object with CIF value, duty, anti-dumping, VAT, and total landed cost. This uses the same calculation as the standalone POST /v1/landed-cost endpoint.
Risk grading & documents: A completed result also includes risk_grading (regulatory intensity level L0–L6 for the HS code, plus CBAM / anti-dumping / sanctions flags) and documents (the required import documents per applicable regime). Both carry an available boolean: true alongside the data, or false with a reason (no_hs_code or error), so you can distinguish "no obligations" from "could not be computed". These objects are returned by this endpoint and by GET /v1/classifications/{id} — the webhook itself only signals that the result is ready.
Example Request
curl -X POST https://import8.nl/api/v1/classify \
-H "Authorization: Bearer c8_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"description": "Wireless bluetooth over-ear headphones with noise cancellation",
"image_url": "https://example.com/product.jpg",
"origin_country": "CN",
"destination_country": "NL",
"goods_value": 25.00,
"quantity": 100,
"shipping_cost": 150.00,
"insurance_cost": 12.50,
"currency": "EUR"
}'
import requests
response = requests.post(
"https://import8.nl/api/v1/classify",
headers={"Authorization": "Bearer c8_your_api_key"},
json={
"description": "Wireless bluetooth over-ear headphones with noise cancellation",
"image_url": "https://example.com/product.jpg",
"origin_country": "CN",
"destination_country": "NL",
"goods_value": 25.00,
"quantity": 100,
"shipping_cost": 150.00,
"insurance_cost": 12.50,
"currency": "EUR"
}
)
data = response.json()
classification_id = data["data"]["id"]
print(f"Classification queued: {classification_id}")
const response = await fetch('https://import8.nl/api/v1/classify', {
method: 'POST',
headers: {
'Authorization': 'Bearer c8_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
description: 'Wireless bluetooth over-ear headphones with noise cancellation',
image_url: 'https://example.com/product.jpg',
origin_country: 'CN',
destination_country: 'NL',
goods_value: 25.00,
quantity: 100,
shipping_cost: 150.00,
insurance_cost: 12.50,
currency: 'EUR',
}),
});
const { data } = await response.json();
console.log('Classification queued:', data.id);
Response 202 Accepted
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"message": "Classification queued. Poll GET /v1/classifications/{id} for results, or configure a webhook to receive notifications."
},
"usage": {
"credits_remaining": 47
}
}
/v1/classifications/{id}
Retrieve a classification result by its UUID. Poll this endpoint after submitting a classification request until the status changes from pending to completed.
Example Request
curl https://import8.nl/api/v1/classifications/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer c8_your_api_key"
import requests
import time
classification_id = "550e8400-e29b-41d4-a716-446655440000"
# Poll until completed (typically 25-35 seconds)
while True:
response = requests.get(
f"https://import8.nl/api/v1/classifications/{classification_id}",
headers={"Authorization": "Bearer c8_your_api_key"}
)
data = response.json()["data"]
if data["status"] == "completed":
print(f"HS Code: {data['hs_code']['code_8']}")
print(f"Confidence: {data['confidence']}%")
break
elif data["status"] == "failed":
print("Classification failed")
break
time.sleep(5)
const classificationId = '550e8400-e29b-41d4-a716-446655440000';
// Poll until completed
async function pollResult() {
const response = await fetch(
`https://import8.nl/api/v1/classifications/${classificationId}`,
{ headers: { 'Authorization': 'Bearer c8_your_api_key' } }
);
const { data } = await response.json();
if (data.status === 'completed') return data;
if (data.status === 'failed') throw new Error('Classification failed');
await new Promise(r => setTimeout(r, 5000));
return pollResult();
}
Response 200 OK
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"source": "api",
"created_at": "2026-03-03T12:00:00+00:00",
"hs_code": {
"code_6": "8518.30",
"code_8": "8518.30.00",
"description": "Headphones and earphones",
"chapter": "85"
},
"confidence": 92,
"reasoning": "The product is wireless bluetooth headphones, which fall under HS heading 8518 for microphones, loudspeakers, and headphones.",
"decision_tree": [
{"level": "section", "code": "XVI", "description": "Machinery and mechanical appliances"},
{"level": "chapter", "code": "85", "description": "Electrical machinery and equipment"},
{"level": "heading", "code": "8518", "description": "Microphones, loudspeakers, headphones"}
],
"alternative_codes": [
{
"code": "8518.10.00",
"description": "Microphones",
"reasoning": "Would apply if the product includes a built-in microphone as primary function"
}
],
"product_summary": "Wireless bluetooth over-ear headphones with noise cancellation",
"ai_usage": {
"input_tokens": 1234,
"output_tokens": 567,
"cost_usd": 0.00408
},
"completed_at": "2026-03-03T12:00:30+00:00",
"landed_cost": {
"cif_value": 2662.50,
"duty": {
"rate": "0.0%",
"amount": 0,
"type": "mfn"
},
"anti_dumping": {
"rate": "0.0%",
"amount": 0
},
"vat": {
"rate": "21.00%",
"amount": 559.13
},
"total_landed_cost": 3221.63,
"currency": "EUR"
},
"risk_assessment": {
"level": "medium",
"reasons": ["Electronics — CE marking, RoHS, WEEE registration and EMC compliance required"],
"should_warn": true,
"recommended_action": "Re-run this classification in Audit mode for a defensible decision trail, or consult a customs broker."
},
"risk_grading": {
"available": true,
"overall_level": 2,
"overall_label": "Conformity",
"trade8_tier": "free",
"cbam_applicable": false,
"anti_dumping_applicable": false,
"sanctions_applicable": false,
"dual_use_applicable": false,
"elements": [
{
"regime": "Electromagnetic Compatibility (EMC)",
"level": 2,
"label": "Conformity",
"applies": true,
"obligations": ["Affix CE marking and draw up an EU Declaration of Conformity"],
"legal_ref": "Directive 2014/30/EU",
"source_url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32014L0030",
"verified_at": "2026-05-20",
"national_authority": null
}
],
"rules_version": "v1.0.1"
},
"documents": {
"available": true,
"items": [
{
"name": "EU Declaration of Conformity (DoC)",
"status": "verplicht-EU",
"legal_ref": "Directive 2014/30/EU, Art. 14",
"source_url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32014L0030",
"source_quote": "The EU declaration of conformity shall state that the fulfilment of the essential requirements has been demonstrated.",
"national_authority": null,
"verified_at": "2026-05-20",
"regime_code": "EMC_2014_30",
"regime_name": "Electromagnetic Compatibility (EMC)"
}
]
}
},
"usage": {
"credits_remaining": 47
}
}
/v1/classifications
List all classifications for your organization, sorted by newest first. Results are paginated.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| per_page | integer | 20 | Number of results per page (max 100) |
Response 200 OK
{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"product_summary": "Wireless bluetooth over-ear headphones",
"hs_code": "8518.30.00",
"confidence": 92,
"created_at": "2026-03-03T12:00:00+00:00"
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 42,
"last_page": 3
},
"usage": {
"credits_remaining": 47
}
}
/v1/classify/batch
Submit up to 100 products for classification in a single request. Each item is processed individually and asynchronously. Returns a batch ID to track overall progress.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| items | array | required | Array of classification requests (max 100). Each item has the same fields as POST /v1/classify. |
Example Request
curl -X POST https://import8.nl/api/v1/classify/batch \
-H "Authorization: Bearer c8_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"items": [
{"description": "Wireless bluetooth headphones", "origin_country": "CN"},
{"description": "Stainless steel water bottle", "origin_country": "CN"},
{"description": "Cotton t-shirt, men, crew neck", "origin_country": "BD"}
]
}'
response = requests.post(
"https://import8.nl/api/v1/classify/batch",
headers={"Authorization": "Bearer c8_your_api_key"},
json={
"items": [
{"description": "Wireless bluetooth headphones", "origin_country": "CN"},
{"description": "Stainless steel water bottle", "origin_country": "CN"},
{"description": "Cotton t-shirt, men, crew neck", "origin_country": "BD"}
]
}
)
batch_id = response.json()["data"]["batch_id"]
const response = await fetch('https://import8.nl/api/v1/classify/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer c8_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: [
{ description: 'Wireless bluetooth headphones', origin_country: 'CN' },
{ description: 'Stainless steel water bottle', origin_country: 'CN' },
{ description: 'Cotton t-shirt, men, crew neck', origin_country: 'BD' },
]
}),
});
Response 202 Accepted
{
"success": true,
"data": {
"batch_id": "batch_abc123",
"total": 3,
"classification_ids": [
"550e8400-e29b-41d4-a716-446655440001",
"550e8400-e29b-41d4-a716-446655440002",
"550e8400-e29b-41d4-a716-446655440003"
]
},
"usage": {
"credits_remaining": 47
}
}
/v1/classify/batch/{batch_id}
Check the status of a batch classification request. Returns the status of each individual classification in the batch.
Response 200 OK
{
"success": true,
"data": {
"batch_id": "batch_abc123",
"total": 3,
"completed": 2,
"pending": 1,
"failed": 0,
"classifications": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"status": "completed",
"hs_code": "8518.30.00"
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"status": "completed",
"hs_code": "7323.93.00"
},
{
"id": "550e8400-e29b-41d4-a716-446655440003",
"status": "pending",
"hs_code": null
}
]
},
"usage": {
"credits_remaining": 44
}
}
/v1/classifications/{id}/feedback
Submit feedback on a classification result. Confirm the classification was correct or provide the correct HS code. This helps improve the system over time.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | string | required | confirmed or corrected |
| corrected_code | string | if corrected | The correct HS code (required when status is corrected) |
Example: Confirm
curl -X POST https://import8.nl/api/v1/classifications/550e8400-.../feedback \
-H "Authorization: Bearer c8_your_api_key" \
-H "Content-Type: application/json" \
-d '{"status": "confirmed"}'
Example: Correct
curl -X POST https://import8.nl/api/v1/classifications/550e8400-.../feedback \
-H "Authorization: Bearer c8_your_api_key" \
-H "Content-Type: application/json" \
-d '{"status": "corrected", "corrected_code": "8518.10.00"}'
/v1/usage
Get current usage statistics for your organization in the current billing period.
Response 200 OK
{
"success": true,
"data": {
"plan": {
"name": "Starter",
"display_name": "Starter"
},
"classifications": {
"current": 42,
"limit": 500,
"remaining": 458,
"percentage": 8
},
"period": {
"year": 2026,
"month": 3
}
}
}
/v1/me
Get account information for the authenticated API key, including organization details and credit balance.
Response 200 OK
{
"success": true,
"data": {
"organization": {
"name": "Acme Trading Co.",
"email": "api@acmetrading.com",
"country": "NL"
},
"credits": {
"remaining": 247,
"total_purchased": 250,
"total_used": 3,
"earliest_expiry": "2027-03-26T00:00:00+00:00"
},
"limits": {
"rate_limit_per_minute": 20,
"max_api_keys": 3
}
}
}
/health
Check the API status. This endpoint does not require authentication.
curl https://import8.nl/api/health
# Response:
{
"success": true,
"data": {
"status": "operational"
}
}
/v1/duty-rates/{hs_code}
Retrieve duty rates for a 10-digit HS code. Returns MFN duties, preferential duties, anti-dumping duties, and VAT rate.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| origin | string | 2-letter ISO country code to filter preferential duties |
| destination | string | 2-letter ISO country code for VAT calculation (default: NL) |
Example
curl "https://import8.nl/api/v1/duty-rates/8518300090?origin=CN&destination=NL" \
-H "Authorization: Bearer c8_your_api_key"
Response 200 OK
{
"success": true,
"data": {
"hs_code": "8518300090",
"description": "Headphones and earphones",
"mfn_duty": {
"rate": "2.00%",
"expression": "2.000 %"
},
"preferential_duties": [
{
"scheme": "GSP",
"rate": "0.00%",
"countries": ["CN", "VN", "BD"]
}
],
"anti_dumping": [],
"vat_rate": "21.00%"
}
}
/v1/landed-cost
Calculate total landed cost for importing a product. Combines CIF value, duties, anti-dumping duties, and VAT.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| hs_code | string | required | 10-digit HS commodity code |
| origin_country | string | required | 2-letter ISO country code |
| destination_country | string | optional | Default: NL |
| value | number | required | Product value (min: 0.01) |
| currency | string | optional | Default: EUR |
| shipping | number | optional | Shipping costs (default: 0) |
| insurance | number | optional | Insurance costs (default: 0) |
Response 200 OK
{
"success": true,
"data": {
"cif_value": 31.50,
"duty": {
"rate": "0.00%",
"amount": 0.00,
"type": "preferential"
},
"anti_dumping": {
"rate": "0.00%",
"amount": 0.00
},
"vat": {
"rate": "21.00%",
"amount": 6.62
},
"total_landed_cost": 38.12,
"currency": "EUR"
}
}
/v1/webhook
Configure a webhook URL to receive push notifications when classifications complete or fail.
Note: The webhook is a notification only — it carries the classification id, not the result. Fetch the full data via GET /v1/classifications/{id}.
Example
curl -X PUT https://import8.nl/api/v1/webhook \
-H "Authorization: Bearer c8_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/webhook"}'
Response 200 OK
{
"success": true,
"data": {
"url": "https://example.com/webhook"
}
}
/v1/webhook
Remove the configured webhook. You will stop receiving notifications.
curl -X DELETE https://import8.nl/api/v1/webhook \
-H "Authorization: Bearer c8_your_api_key"
# Response:
{
"success": true,
"message": "Webhook removed."
}
Webhook Events
When a classification finishes processing, Import8 sends a small POST notification to your webhook URL, with the event type in the X-Import8-Event header.
| Event | Description |
|---|---|
| classification.completed | Classification finished successfully |
| classification.failed | Classification failed due to an error |
Headers
| Header | Description |
|---|---|
| X-Import8-Event | Event type (e.g. classification.completed) |
| User-Agent | Import8-Webhook/1.0 |
Example Payload
{
"event": "classification.completed",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed"
}
}
The webhook is a signal, not the data. It only tells you a classification is ready (or failed) and carries its id. Fetch the full result — HS code, risk grading, documents — via the authenticated GET /v1/classifications/{id}. Because the notification contains no sensitive data and the result sits behind your API key, the webhook is not signed.
Rate Limits
Rate limits are applied per API key and depend on your credit package tier. Every API response includes rate limit headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Max requests per window |
| X-RateLimit-Remaining | Remaining requests in current window |
| X-RateLimit-Reset | Unix timestamp when window resets |
When exceeded, the API returns 429 Too Many Requests with a Retry-After header.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
{
"success": false,
"message": "Rate limit exceeded. Please retry after 30 seconds."
}
Error Codes
The API uses standard HTTP status codes:
| Status | Meaning |
|---|---|
| 200 | Success |
| 202 | Accepted -- classification queued |
| 401 | Invalid or missing API key |
| 404 | Resource not found |
| 422 | Validation error -- check request body |
| 429 | Rate limit or usage limit exceeded |
| 500 | Server error |
Error Response Format
{
"success": false,
"message": "Description of the error"
}