TL;DR
- One endpoint:
GET https://api.tandom.ai/v1/duty/calculatereturns 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/copperplusmeltpour/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 ofRUor "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 carriescode,authority,authorityType,rate,amount,citation,aceReportingOrder, anddisplayLabel. Render this list as the line-level breakdown.adcvdCases[]: matches against the active AD/CVD order list. Each carriescaseNumber,orderType(AD or CVD),matchType(exact, subheading, heading, advisory),productDescription,rate(when published),frCitation, andcaseNumberUrl.matchedExclusions[]: Chapter 99 exclusions the engine surfaced for the importer to consider. Each hasactionable(true means the importer can claim it),conditionStatus, anddisplayDescription.mpf,hmf: fee objects withrate,amount, andapplicablebooleans.conditionalFieldsandmissingConditionalInputs[]: 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.
| 7318.15.80.66MFN base, Cap screws (Column 1 General) | MFN | 8.5% | $4,250.00 |
| 9903.88.03Section 301 List 3 (USTR Sept 2024 four-year review) | S301 | 25% | $12,500.00 |
| 9903.82.02Section 232 steel (Proc 10908, post-April 2026 family) | S232 | 50% | $25,000.00 |
| 9903.03.01Section 122 (mutual-exclusion with S232; not stacked) | S122 | 0% | $0.00 |
| n/aMPF (19 CFR 24.23, within FY26 cap) | MPF | 0.3464% | $173.20 |
| n/aHMF (19 USC 4461, ocean only) | HMF | 0.125% | $62.50 |
| Total duty + fees (before AD/CVD scope check) | $41,985.70 | ||
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: 19903.03.01(Section 122 surcharge family)aceReportingOrder: 29903.88.03(Section 301 List 3)aceReportingOrder: 39903.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.