API Reference
Integrate Dutiable HS code classification and tariff data directly into your codebase. All endpoints accept and return JSON.
Base URL
All API endpoints are prefixed with /api/v1.
Authentication
Pass your API key in the Authorization header as a Bearer token, or in the x-api-key header. API keys are generated from Settings → Business Workspace → Create API key.
# Bearer token (recommended) curl https://dutiable.io/api/v1/catalog \ -H "Authorization: Bearer sk_live_your_key_here" # x-api-key header curl https://dutiable.io/api/v1/catalog \ -H "x-api-key: sk_live_your_key_here"
API keys begin with sk_live_. Keep them secret — they grant full access to your account.
Errors
All errors return a JSON body with an error string.
{ "error": "Missing API key." }Create a classification job
/api/v1/classifySubmit a batch of products for HS code classification. Returns a jobId immediately — use the GET endpoint to poll for results.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| rows | array | required | Array of product objects. Max 500 per job. |
| rows[].productName | string | required | Product name (required for all rows). |
| rows[].sku | string | optional | Your internal SKU. Used as the catalog key when present. |
| rows[].description | string | optional | Detailed product description — improves accuracy. |
| rows[].material | string | optional | Primary material (e.g. cotton, polypropylene). |
| rows[].category | string | optional | Product category (e.g. apparel, electronics). |
| rows[].originCountry | string | optional | Country of manufacture — used for Section 301 risk. |
Request example
curl -X POST https://dutiable.io/api/v1/classify \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"rows": [
{
"sku": "SKU-001",
"productName": "Wireless Bluetooth Headphones",
"description": "Over-ear headphones with ANC, 30hr battery",
"material": "plastic, silicone",
"originCountry": "China"
},
{
"sku": "SKU-002",
"productName": "Cotton T-Shirt",
"material": "100% cotton",
"category": "apparel"
}
]
}'Response
{
"jobId": "job_01hz...",
"status": "queued",
"totalRows": 2,
"message": "Job queued. Poll GET /api/v1/classify/job_01hz... for results."
}Get job results
/api/v1/classify/:jobIdPoll for classification results. Jobs typically complete in 2–30 seconds depending on batch size. Status transitions: queued → processing → done (or failed).
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| jobId | string | required | The jobId returned by POST /api/v1/classify. |
Request example
curl https://dutiable.io/api/v1/classify/job_01hz... \ -H "Authorization: Bearer sk_live_..."
Response
{
"job": {
"id": "job_01hz...",
"status": "done",
"totalRows": 2,
"processedRows": 2,
"summary": {
"classified": 2,
"review": 0,
"flagged": 0,
"failed": 0
}
},
"rows": [
{
"id": 1,
"sku": "SKU-001",
"productName": "Wireless Bluetooth Headphones",
"hsCode": "8518.30.20",
"confidence": 92,
"euDuty": "3.7%",
"usDuty": "0%",
"ukDuty": "3.5%",
"status": "classified",
"fromCache": false,
"meta": {
"htsUS": "8518302000",
"cnEU": "85183020",
"ukTariff": "8518300020",
"chapterTitle": "Electrical machinery and equipment",
"headingTitle": "8518 — Headphones and earphones",
"applicableRule": "GRI 1",
"alternativeCode": null,
"alternativeReason": null
}
},
{
"id": 2,
"sku": "SKU-002",
"productName": "Cotton T-Shirt",
"hsCode": "6109.10.00",
"confidence": 88,
"euDuty": "12%",
"usDuty": "16.5%",
"ukDuty": "12%",
"status": "classified",
"fromCache": false
}
]
}Polling pattern (JavaScript)
async function waitForJob(jobId: string, apiKey: string) {
const headers = { Authorization: `Bearer ${apiKey}` };
for (let i = 0; i < 30; i++) {
const res = await fetch(`https://dutiable.io/api/v1/classify/${jobId}`, { headers });
const data = await res.json();
if (data.job.status === "done" || data.job.status === "failed") {
return data;
}
await new Promise((r) => setTimeout(r, 2000)); // wait 2s between polls
}
throw new Error("Job timed out");
}Get product by SKU
/api/v1/products/:sku/hs-codeRetrieve the most recent classification for a product by SKU. Returns the latest result from your job history.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sku | string | required | Your product SKU. |
Request example
curl https://dutiable.io/api/v1/products/SKU-001/hs-code \ -H "Authorization: Bearer sk_live_..."
Response
{
"sku": "SKU-001",
"productName": "Wireless Bluetooth Headphones",
"hsCode": "8518.30.20",
"confidence": 92,
"euDuty": "3.7%",
"usDuty": "0%",
"ukDuty": "3.5%",
"status": "classified",
"classifiedAt": "2026-04-09T14:05:00Z"
}List catalog
/api/v1/catalogReturns all products in your catalog — the persistent, deduplicated store of every classified product. Updated automatically after every job.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| audit | boolean | optional | Set to "true" to include full audit history per product. |
Request example
# Basic list curl https://dutiable.io/api/v1/catalog \ -H "Authorization: Bearer sk_live_..." # With audit history curl "https://dutiable.io/api/v1/catalog?audit=true" \ -H "Authorization: Bearer sk_live_..."
Response
{
"total": 1,
"products": [
{
"catalogKey": "SKU-001",
"sku": "SKU-001",
"productName": "Wireless Bluetooth Headphones",
"hsCode": "8518.30.20",
"confidence": 92,
"euDuty": "3.7%",
"usDuty": "0%",
"ukDuty": "3.5%",
"status": "classified",
"updatedAt": "2026-04-09T14:05:00Z",
"createdAt": "2026-04-08T10:22:00Z"
}
]
}Products without a SKU use a 16-character hash of the product name as their catalogKey.
Get alerts
/api/v1/alertsReturns tariff change alerts for the HS codes in your catalog. Only codes from your classified products are monitored.
Request example
curl https://dutiable.io/api/v1/alerts \ -H "Authorization: Bearer sk_live_..."
Response
{
"plan": "business",
"alerts": [
{
"id": "42-1",
"sku": "SKU-001",
"productName": "Wireless Bluetooth Headphones",
"hsCode": "8518.30.20",
"market": "US",
"changeType": "duty_change",
"oldDuty": "0%",
"newDuty": "7.5%",
"detectedAt": "2026-04-09T08:00:00Z",
"severity": "critical"
}
],
"trackedProducts": [
{
"id": 1,
"sku": "SKU-001",
"productName": "Wireless Bluetooth Headphones",
"hsCode": "8518.30.20",
"status": "changed",
"lastChecked": "2026-04-09T08:00:00Z"
}
]
}Webhooks
Configure a Slack webhook to receive alerts when tariff rates change for your products.
/api/v1/webhooksReturns current webhook configuration.
/api/v1/webhooksConfigure a Slack incoming webhook URL.
Request body (POST)
| Parameter | Type | Required | Description |
|---|---|---|---|
| webhookUrl | string | required | A valid Slack incoming webhook URL (https://hooks.slack.com/services/...). |
Example
curl -X POST https://dutiable.io/api/v1/webhooks \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "webhookUrl": "https://hooks.slack.com/services/T.../B.../..." }'{ "ok": true, "slackConfigured": true }End-to-end example
A complete workflow: classify products, wait for results, then store the HS codes in your system.
const API_KEY = process.env.DUTIABLE_API_KEY;
const BASE = "https://dutiable.io/api/v1";
const headers = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
// 1. Submit products for classification
const { jobId } = await fetch(`${BASE}/classify`, {
method: "POST",
headers,
body: JSON.stringify({
rows: [
{ sku: "SKU-001", productName: "Wireless Bluetooth Headphones", originCountry: "China" },
{ sku: "SKU-002", productName: "Cotton T-Shirt", material: "100% cotton" },
],
}),
}).then((r) => r.json());
// 2. Poll until done
let results;
for (let i = 0; i < 30; i++) {
results = await fetch(`${BASE}/classify/${jobId}`, { headers }).then((r) => r.json());
if (results.job.status === "done") break;
await new Promise((r) => setTimeout(r, 2000));
}
// 3. Use the results
for (const row of results.rows) {
console.log(`${row.sku}: ${row.hsCode} (confidence ${row.confidence}%)`);
console.log(` EU duty: ${row.euDuty}, US duty: ${row.usDuty}`);
}
// 4. Fetch your full catalog at any time
const { products } = await fetch(`${BASE}/catalog`, { headers }).then((r) => r.json());
console.log(`Catalog has ${products.length} classified products`);