Guide

How to wire the Tandom Duty Calculator API into a TMS or broker software

Drop the Tandom Duty Calculator API into a TMS, broker software, or in-house ERP. Code samples, the response shape, and ACE reporting order, in 2026.

Updated 11 min readGet an API key
Share:

TL;DR

  • One endpoint: GET https://api.tandom.ai/v1/duty/calculate returns the full landed-duty stack for one HTS line. Same handler powers the public calculator at tariffs.tandom.ai/calculator.
  • Five required inputs: hts (10-digit), origin (ISO 2), value (USD), date (ISO), and the metal-content fields when Section 232 reaches the chapter (steel / aluminum / copper plus meltpour / uscast).
  • Response carries everything CBP wants: per-layer dollar amounts, every applicable Chapter 99 code in ACE reporting order, AD/CVD case matches with manufacturer-specific rates, MPF, HMF, and a regulatory citation per line.
  • Drop into a TMS or broker software in an afternoon. The worked example below runs the live API, shows the JSON response, and maps it into a fictional TMS line record that you can paste into your integration.

The endpoint

One endpoint, one method, one HTS line per call.

GET https://api.tandom.ai/v1/duty/calculate
Authorization: Bearer tk_live_...

The same handler is mounted on the public calculator deployment at https://tariffs.tandom.ai/api/duty/calculate. It is rate-limited differently (browser-origin, no key) but useful for spot checks against the same engine your integration uses.

Free tier

Free tier on api.tandom.ai includes 1,000 duty calculations per month. The endpoint is DB plus arithmetic, no Claude calls on the request path, so latency is consistently in the 100 to 400 ms range for warm caches. Sign up at api.tandom.ai.

Idempotency and retries

GET calls are idempotent by definition. For POST endpoints (such as the closed-beta /v1/duty/calculate/batch), pass Idempotency-Key to deduplicate retries within a 24-hour window.

Request shape

All inputs are query parameters. Only hts is strictly required; everything else is optional but gates which layers and fees apply.

Core inputs

  • hts (required): 10-digit HTSUS code, dotted form (7318.15.80.66) or undotted (7318158066). Both work.
  • origin: ISO 3166-1 alpha-2 country code. Drives Section 301 (CN), Column 2 (CU/KP/BY/RU), and AD/CVD country scoping.
  • value: customs value in USD per 19 USC 1401a. Drives every ad-valorem amount.
  • date: entry date, ISO format (YYYY-MM-DD). Defaults to today. Critical for backdated quotes and post-summary corrections.

Section 232 inputs

  • steel / aluminum / copper: content percentages (0 to 100). For derivatives in HTS Chapters 73, 76, 82, 83, 84, 85, and 87, this is what gates the 50% Section 232 layer.
  • meltpour: steel melt-and-pour country (ISO 2). Drives the 25% UK rate, the 10% Brazil carve-out, and other country-specific 232 rates.
  • uscast: aluminum smelt-and-cast country (ISO 2). A value of RU or "unknown" routes to the 200% rate at 9903.85.67/.68, in effect since June 28, 2025.

Other optional inputs

  • spi: Special Programs Indicator letter (S, KR, JP, D, IL, etc.). Surfaces preference-program offsets when available.
  • transport: OCEAN, AIR, TRUCK, RAIL. Toggles HMF.
  • qty / qty2: primary and secondary unit quantities, for HTS lines that carry specific or compound rates (mostly textiles, footwear, agriculture).
  • exclusions: comma-separated Chapter 99 exclusion codes the importer is claiming (e.g., 9903.88.6x).

Response shape

The response is one top-level dutyInfo object. The fields you will care about for a TMS or broker UI:

  • generalRate, section232, section301: the headline rate strings (e.g. "8.5%", "50%").
  • mfnAmount, adcvdAmount, totalDutyAmount, totalLandedDuty, effectiveRate: the dollar-amount totals.
  • chapter99Sources[]: the array of secondary classifications. Each carries code, authority, authorityType, rate, amount, citation, aceReportingOrder, and displayLabel. Render this list as the line-level breakdown.
  • adcvdCases[]: matches against the active AD/CVD order list. Each carries caseNumber, orderType (AD or CVD), matchType (exact, subheading, heading, advisory), productDescription, rate (when published), frCitation, and caseNumberUrl.
  • matchedExclusions[]: Chapter 99 exclusions the engine surfaced for the importer to consider. Each has actionable (true means the importer can claim it), conditionStatus, and displayDescription.
  • mpf, hmf: fee objects with rate, amount, and applicable booleans.
  • conditionalFields and missingConditionalInputs[]: tell your UI which form fields the engine still wants. Bind these to field visibility to mirror the calculator's behavior.

Every chapter99Sources entry includes a regulatory citation (e.g. "19 USC §1862, Proclamation 10908") so a UI can render the authority next to the rate. No separate lookup needed.

Worked example

Cap screws of iron or steel from China, 100% steel content, melt and pour in China, $50,000 declared value, May 1, 2026 entry, ocean mode. The same line item the duty-calc guide walks through.

1. The curl call

curl -sS "https://api.tandom.ai/v1/duty/calculate?\
hts=7318.15.80.66&\
origin=CN&\
value=50000&\
date=2026-05-01&\
steel=100&\
meltpour=CN" \
  -H "Authorization: Bearer tk_live_..." \
  | jq .

2. The response (trimmed)

The JSON is large; this is the subset that drives a TMS line.

{
  "dutyInfo": {
    "htsCode": "7318.15.80.66",
    "description": "Cap screws",
    "declaredValue": 50000,
    "generalRate": "8.5%",
    "section232": "50%",
    "section301": "25%",
    "mfnAmount": 4250,
    "adcvdAmount": 0,
    "entryDutiesAmount": 41750,
    "totalDutyAmount": 41750,
    "effectiveRate": 83.5,
    "chapter99Sources": [
      {
        "code": "9903.03.01",
        "authority": "Section 122 Surcharge",
        "authorityType": "section_122",
        "rate": 0,
        "amount": 0,
        "citation": "19 USC §2132, Trade Act of 1974 §122",
        "aceReportingOrder": 1,
        "displayLabel": "Section 122"
      },
      {
        "code": "9903.88.03",
        "authority": "Section 301 List 3",
        "authorityType": "section_301",
        "rate": 25,
        "amount": 12500,
        "citation": "19 USC §2411, Trade Act of 1974",
        "aceReportingOrder": 2,
        "displayLabel": "Section 301"
      },
      {
        "code": "9903.82.02",
        "authority": "section_232",
        "authorityType": "section_232",
        "rate": 50,
        "amount": 25000,
        "citation": "19 USC §1862, Proclamation 10908",
        "aceReportingOrder": 3,
        "material": "steel",
        "displayLabel": "Section 232 Steel"
      }
    ],
    "adcvdCases": [
      {
        "caseNumber": "A-570-932",
        "orderType": "AD",
        "matchType": "heading",
        "productDescription": "Steel Threaded Rod",
        "advisory": true,
        "headingMatched": "7318",
        "frCitation": "E9-8630",
        "caseNumberUrl": "https://access.trade.gov/case_doc/?case_number=A-570-932"
      }
    ],
    "mpf": { "rate": 0.003464, "amount": 173.2 },
    "hmf": { "rate": 0.00125, "amount": 62.5, "applicable": true },
    "totalLandedDuty": 41985.7
  }
}

The numbers above are the live response from the Tandom engine on May 5, 2026. Every dollar amount is verified against the calculator UI at tariffs.tandom.ai/calculator for the same inputs.

3. The line in the calculator's visual identity

The same response, rendered with the calculator's FilingLineBlock visual.

Line 1Cap screws (7318.15.80.66), China origin, 100% steel, melt/pour CN
Declared Value$50,000
7318.15.80.66MFN base, Cap screws (Column 1 General)8.5%$4,250.00
9903.88.03Section 301 List 3 (USTR Sept 2024 four-year review)25%$12,500.00
9903.82.02Section 232 steel (Proc 10908, post-April 2026 family)50%$25,000.00
9903.03.01Section 122 (mutual-exclusion with S232; not stacked)0%$0.00
n/aMPF (19 CFR 24.23, within FY26 cap)0.3464%$173.20
n/aHMF (19 USC 4461, ocean only)0.125%$62.50
Total duty + fees (before AD/CVD scope check)$41,985.70
Layer contribution$41,985.70 on $50,000
MFN$4,250.00S301$12,500.00S232$25,000.00MPF$173.20HMF$62.50

Effective rate before the AD/CVD scope check: 83.5% on a $50,000 invoice. The Section 232 layer carries 60% of the duty bill, Section 301 carries 30%, and the MFN base is just over 10%. The AD/CVD advisory match (A-570-932 Steel Threaded Rod) is a heading-level flag at 7318. Cap screws (7318.15.80) are almost certainly outside the threaded-rod scope, but the broker owns that determination.

Mapping to a TMS line

A typical TMS or broker software has a per-line record on the entry summary that captures HTS code, declared value, layered duties, and a regulatory-citation field. The Tandom response maps cleanly. Here is a TypeScript sketch you can paste into your integration.

The fetch

type TandomDutyResponse = {
  dutyInfo: {
    htsCode: string;
    description: string;
    declaredValue: number;
    generalRate: string;
    section232?: string;
    section301?: string;
    mfnAmount: number;
    adcvdAmount: number;
    entryDutiesAmount: number;
    totalDutyAmount: number;
    effectiveRate: number;
    chapter99Sources: Array<{
      code: string;
      authority: string;
      authorityType: string;
      rate: number;
      amount: number;
      citation: string;
      aceReportingOrder: number;
      displayLabel: string;
    }>;
    adcvdCases: Array<{
      caseNumber: string;
      orderType: "AD" | "CVD";
      matchType: "exact" | "subheading" | "heading" | "advisory";
      productDescription: string;
      advisory: boolean;
      rate: number | null;
      caseNumberUrl: string;
    }>;
    mpf: { rate: number; amount: number };
    hmf: { rate: number; amount: number; applicable: boolean };
    totalLandedDuty: number;
  };
};

async function calculateDuty(input: {
  hts: string;
  origin: string;
  value: number;
  date: string;
  steel?: number;
  meltpour?: string;
}): Promise<TandomDutyResponse> {
  const params = new URLSearchParams({
    hts: input.hts,
    origin: input.origin,
    value: String(input.value),
    date: input.date,
    ...(input.steel !== undefined && { steel: String(input.steel) }),
    ...(input.meltpour && { meltpour: input.meltpour }),
  });

  const res = await fetch(
    `https://api.tandom.ai/v1/duty/calculate?${params}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.TANDOM_API_KEY}`,
      },
    }
  );

  if (!res.ok) {
    throw new Error(`Tandom API error ${res.status}: ${await res.text()}`);
  }

  return res.json();
}

Mapping into a TMS line record

type TMSLine = {
  lineNumber: number;
  htsCode: string;
  description: string;
  declaredValueUSD: number;
  duties: Array<{
    layer: string;          // "MFN", "S232", "S301", "S122"
    chapter99Code?: string; // "9903.82.02" etc, undefined for MFN
    rate: string;           // "50%"
    amountUSD: number;
    citation: string;
    aceOrder?: number;
  }>;
  fees: { mpfUSD: number; hmfUSD: number };
  adcvdAdvisory: Array<{
    caseNumber: string;
    productDescription: string;
    matchType: string;
    sourceUrl: string;
  }>;
  totalLandedDutyUSD: number;
};

function toTMSLine(
  apiRes: TandomDutyResponse,
  lineNumber: number
): TMSLine {
  const d = apiRes.dutyInfo;
  return {
    lineNumber,
    htsCode: d.htsCode,
    description: d.description,
    declaredValueUSD: d.declaredValue,
    duties: [
      {
        layer: "MFN",
        rate: d.generalRate,
        amountUSD: d.mfnAmount,
        citation: `HTSUS ${d.htsCode}, Column 1 General`,
      },
      ...d.chapter99Sources
        .filter((s) => s.amount > 0)
        .map((s) => ({
          layer: s.displayLabel,
          chapter99Code: s.code,
          rate: `${s.rate}%`,
          amountUSD: s.amount,
          citation: s.citation,
          aceOrder: s.aceReportingOrder,
        })),
    ],
    fees: {
      mpfUSD: d.mpf.amount,
      hmfUSD: d.hmf.applicable ? d.hmf.amount : 0,
    },
    adcvdAdvisory: d.adcvdCases.map((c) => ({
      caseNumber: c.caseNumber,
      productDescription: c.productDescription,
      matchType: c.matchType,
      sourceUrl: c.caseNumberUrl,
    })),
    totalLandedDutyUSD: d.totalLandedDuty,
  };
}

For the worked-example call, toTMSLine(...) produces a line with four duty entries (MFN, S301, S232, plus the zero-amount S122 if you do not filter it), MPF $173.20, HMF $62.50, one AD/CVD advisory entry pointing at A-570-932, and a total landed duty of $41,985.70. That is the exact contract a broker workstation needs to draft an entry summary draft and walk it past a senior reviewer.

ACE reporting order

ACE expects Chapter 99 secondary classifications in a specific order on the entry line. The Tandom engine returns each chapter99Sources[] entry with an aceReportingOrder integer that is 1-indexed and matches CBP's expected order. Sort and post in ascending order.

For the worked-example response, the Chapter 99 codes come back as:

  • aceReportingOrder: 1 9903.03.01 (Section 122 surcharge family)
  • aceReportingOrder: 2 9903.88.03 (Section 301 List 3)
  • aceReportingOrder: 3 9903.82.02 (Section 232 steel, post-April 6, 2026 consolidated family)

For pre-April-6, 2026 entry dates, the engine returns the legacy steel codes (9903.80.x / 9903.81.x), aluminum (9903.85.x), and copper (9903.78.x) families instead, with the same aceReportingOrder contract. Trust the response, do not re-derive the family from the entry date in your TMS code. The engine handles the cutover.

Why this matters

Posting the Chapter 99 codes out of order is one of the most common ACE rejection reasons brokers see. Reading aceReportingOrder directly from the response removes the rule from your code and parks it where it belongs (in the data layer of an engine that updates with every CBP CSMS message).

Common pitfalls

The mistakes that bite TMS and broker integrations most often.

Skipping the entry date

The date parameter defaults to today. For backdated quotes, post-summary corrections, or any historical reporting, always pass it explicitly. Section 232 doublings, Section 301 four-year-review increases, and the Section 122 surcharge window all hinge on entry date per 19 CFR 141.69.

Re-deriving the layer order in your code

ACE expects Chapter 99 codes in a specific order. Use the aceReportingOrder field on each entry, not your own ordering logic. The engine handles the April 2026 9903.82 consolidation; if your code re-orders by code prefix, it will get the cutover wrong.

Treating heading-level AD/CVD matches as confirmed

When matchType is "heading" or advisory: true, the calculator is flagging a candidate for scope review, not an assessment. The HTS codes in an AD/CVD order are advisory; the narrative scope description in the Federal Register notice is dispositive. Your UI should render heading-level matches as a warning, not a deposit.

Missing the conditional inputs the engine needs

A Section 232 derivative chapter (73, 76, 82-87) needs steel, aluminum, or copper plus the metal-origin field. If you skip them, the response sets missingConditionalInputs[] and the layer is flagged as incomplete. Render that as a warning in the TMS, not as a clean calculation.

Hard-coding rates in the TMS

Brokers who saw the IEEPA-to-Section-122 cutover in February 2026 know how fast a hard-coded rate becomes wrong. The whole point of the API is that the rates are kept current. Map the response to your line record, do not write a parallel rate table.

Confusing duties with fees

MPF and HMF are not duties. They are user fees, returned as their own mpf and hmf objects. They apply on top of every duty layer and are not waived by Section 301 exclusions or Section 232 carve-outs. Render them as a separate fee row in the TMS.

Calling the public calculator URL from production

The handler at tariffs.tandom.ai/api/duty/calculate is fine for spot checks. For production integrations, use api.tandom.ai/v1/duty/calculate with a real API key. The browser-origin endpoint is rate-limited per IP and is not contracted to be stable as a public integration target.

Ignoring matchedExclusions

The response surfaces Chapter 99 exclusions the importer might be eligible to claim under matchedExclusions[]. Each carries an actionable boolean and a conditionStatus. A TMS that ignores this list will quote a higher duty than necessary on lines where a known exclusion would apply on claim.

Section 122 stacking

Section 122 does not stack on Section 232. The engine handles the mutual exclusion automatically. If your TMS shows both layers adding to the total, you are reading the wrong field; trust chapter99Sources[].amount, not your own sum across rate strings.

Aluminum smelt-and-cast unknown

A blank uscast field on aluminum derivatives produces the 200% Russia rate by default since June 28, 2025. If your TMS omits the field for products where the importer never supplied smelt-and-cast country, every aluminum-derivative line bills at 200%. Fail the entry rather than auto-defaulting to the punitive rate.

Glossary

Tandom Duty Calculator API
The REST endpoint at api.tandom.ai/v1/duty/calculate that returns the full landed-duty stack for one HTS line, the same engine that powers the public calculator at tariffs.tandom.ai/calculator.
API key
An Authorization: Bearer tk_live_... token issued from api.tandom.ai. Free tier is 1,000 duty calls per month. Higher tiers are paid.
dutyInfo
The top-level object returned by the API. Contains every rate, dollar amount, layer, AD/CVD case, fee, and conditional-input flag for the queried HTS line.
chapter99Sources
Array on the response carrying the secondary Chapter 99 classifications that apply to the line. Each entry has a code, rate, dollar amount, regulatory citation, and ACE reporting order.
adcvdCases
Array on the response carrying matches against the active AD/CVD order list. matchType (exact, subheading, heading, advisory) tells you how confident the match is. Heading and advisory matches are candidates to investigate, not confirmed assessments.
matchedExclusions
Array on the response carrying Chapter 99 exclusions the importer might claim. The actionable flag is true when the importer can claim it on this line.
aceReportingOrder
1-indexed integer on each chapter99Sources entry. Indicates the order ACE expects the Chapter 99 codes posted on the entry line.
conditionalFields
Object on the response indicating which conditional inputs the engine needs (has232, hasCopper, materialType, needsQuantity, etc.). Bind these to your form-field visibility to mirror the calculator's conditional behavior.
MFN amount
Dollar amount of the Column 1 General duty applied to the line. Returned as mfnAmount.
totalLandedDuty
The grand total of every duty layer plus MPF plus HMF, in USD. What the importer pays at entry, before AD/CVD deposits.
effectiveRate
The total duty as a percentage of declared value. Useful for a quick at-a-glance number in a TMS dashboard.
MPF / HMF
Merchandise Processing Fee (0.3464% on formal entries, FY26 cap) and Harbor Maintenance Fee (0.125% on ocean only). Both returned as their own response objects.

FAQ

High-intent questions developers integrating the Tandom Duty Calculator API ask most often.

What is the base URL for the Tandom Duty Calculator API?
Production calls go to https://api.tandom.ai/v1/duty/calculate. The same handler is reachable on the public calculator deployment at https://tariffs.tandom.ai/api/duty/calculate, which is what you hit when you click the calculator UI. Use api.tandom.ai for integrations; use tariffs.tandom.ai for spot checks against the live calculator.
Is the duty calculator API a GET or POST?
GET, with all inputs as query parameters. Required input is hts (10-digit, dotted form acceptable). Common optional inputs are origin (ISO 2-letter), value (USD), date (ISO date), steel (% steel content), aluminum (% aluminum content), copper (% copper content), meltpour (steel country), uscast (aluminum smelt-and-cast country), spi (preference program letter), and exclusions (Chapter 99 exclusion codes the importer claims).
Does the API return AD/CVD deposit rates?
It returns matches against the active AD/CVD order list in adcvdCases[], with caseNumber, matchType (exact, subheading, heading, or advisory), product description, and a manufacturer-specific rate when one is in the order's published rate list. Heading-level matches are flagged advisory and the broker still owns the scope-text read against the Federal Register notice. The order's caseNumberUrl points at Commerce ACCESS for primary-source verification.
How do I show users the regulatory authority behind each line in my UI?
Each item in chapter99Sources[] carries a citation field (statutory citation), authorityType (section_232, section_301, section_122, etc.), aceReportingOrder (1-indexed ACE position), and displayLabel (the same label the Tandom calculator shows). Map citation under the rate to give users a regulatory-grade UI without a separate lookup.
Does the response include MPF and HMF?
Yes. mpf.amount and hmf.amount are returned as dollar values for the entry. mpf.rate is the ad valorem rate (0.3464% on formal entries in fiscal year 2026) and hmf.rate is 0.125% on ocean shipments. hmf.applicable is false for non-ocean modes. totalLandedDuty already includes both fees.
How do I handle the Section 232 metal-content split (Line 1 plus Line 2)?
Pass steel, aluminum, or copper as a content percentage and meltpour or uscast as the metal origin. The response surfaces the split through chapter99Sources[] and section232 fields. The metal-content layer applies to the value times content-percent, not the entered value of the whole article. CBP's two-line reporting (a non-metal Line 1 carrying the original HTS at full value, plus a metal-content Line 2 at quantity zero with the 9903 code) is the same shape; your TMS posts both lines using the values returned.
What rate does the API use if I skip the entry date?
It defaults to the current date. For backdated quotes or post-summary corrections, always pass date explicitly in ISO format (YYYY-MM-DD). The engine resolves all rates as-of that entry date per 19 CFR 141.69, including Section 232 doublings, Section 301 four-year-review increases, and the Section 122 surcharge window.
Can I batch-calculate an entire entry summary?
GET /v1/duty/calculate handles one HTS line per call, which matches how the duty stack actually computes. For multi-line entries, fan out one call per line and sum the line-level totalDutyAmount values plus a single MPF (capped at the entry-summary level). POST /v1/duty/calculate/batch accepts lines[] and is in closed beta. Most TMS and broker integrations work fine with one call per line, parallelized.
Does the API tell me which conditional inputs the user still needs to supply?
Yes. conditionalFields returns booleans for has232, hasCopper, materialType, needsMaterialSlider, hasReciprocal, needsQuantity, hasAdCvd, showValuation, and needsDepartureDate. missingConditionalInputs[] lists the field names that are still required to produce a complete answer. UIs that bind those flags to form-field visibility get the same conditional behavior the Tandom calculator has, without re-implementing the logic.
Where do I get an API key?
Sign up at api.tandom.ai. The free tier covers 1,000 duty calculations per month. Pass the key as Authorization: Bearer tk_live_... on every call. Idempotency-Key is supported on POSTs for safe retries.
Share: