dutiable.io/Docs
Business plan required

API Reference

Integrate Dutiable HS code classification and tariff data directly into your codebase. All endpoints accept and return JSON.

Base URL

https://dutiable.io

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.

bash
# 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.

401Missing API key.
403Invalid or revoked API key.
400Bad request — missing or malformed parameters.
404Resource not found.
502Upstream classification failed — safe to retry.
json
{ "error": "Missing API key." }

Create a classification job

POST/api/v1/classify

Submit a batch of products for HS code classification. Returns a jobId immediately — use the GET endpoint to poll for results.

Request body

ParameterTypeRequiredDescription
rowsarrayrequiredArray of product objects. Max 500 per job.
rows[].productNamestringrequiredProduct name (required for all rows).
rows[].skustringoptionalYour internal SKU. Used as the catalog key when present.
rows[].descriptionstringoptionalDetailed product description — improves accuracy.
rows[].materialstringoptionalPrimary material (e.g. cotton, polypropylene).
rows[].categorystringoptionalProduct category (e.g. apparel, electronics).
rows[].originCountrystringoptionalCountry of manufacture — used for Section 301 risk.

Request example

bash
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

json
{
  "jobId": "job_01hz...",
  "status": "queued",
  "totalRows": 2,
  "message": "Job queued. Poll GET /api/v1/classify/job_01hz... for results."
}

Get job results

GET/api/v1/classify/:jobId

Poll for classification results. Jobs typically complete in 2–30 seconds depending on batch size. Status transitions: queued → processing → done (or failed).

Path parameters

ParameterTypeRequiredDescription
jobIdstringrequiredThe jobId returned by POST /api/v1/classify.

Request example

bash
curl https://dutiable.io/api/v1/classify/job_01hz... \
  -H "Authorization: Bearer sk_live_..."

Response

json
{
  "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)

typescript
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

GET/api/v1/products/:sku/hs-code

Retrieve the most recent classification for a product by SKU. Returns the latest result from your job history.

Path parameters

ParameterTypeRequiredDescription
skustringrequiredYour product SKU.

Request example

bash
curl https://dutiable.io/api/v1/products/SKU-001/hs-code \
  -H "Authorization: Bearer sk_live_..."

Response

json
{
  "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

GET/api/v1/catalog

Returns all products in your catalog — the persistent, deduplicated store of every classified product. Updated automatically after every job.

Query parameters

ParameterTypeRequiredDescription
auditbooleanoptionalSet to "true" to include full audit history per product.

Request example

bash
# 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

json
{
  "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

GET/api/v1/alerts

Returns tariff change alerts for the HS codes in your catalog. Only codes from your classified products are monitored.

Request example

bash
curl https://dutiable.io/api/v1/alerts \
  -H "Authorization: Bearer sk_live_..."

Response

json
{
  "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.

GET/api/v1/webhooks

Returns current webhook configuration.

POST/api/v1/webhooks

Configure a Slack incoming webhook URL.

Request body (POST)

ParameterTypeRequiredDescription
webhookUrlstringrequiredA valid Slack incoming webhook URL (https://hooks.slack.com/services/...).

Example

bash
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.../..." }'
json
{ "ok": true, "slackConfigured": true }

End-to-end example

A complete workflow: classify products, wait for results, then store the HS codes in your system.

typescript
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`);