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. - Start with four core facts:
hts(10-digit),origin(ISO 2),value(USD), anddate(ISO). Onlyhtsis syntactically required for GET, but the other facts are needed for a useful sourcing or import-cost scenario. Add the metal-content fields when Section 232 reaches the chapter (steel/aluminum/copperplus the supported origin or claim inputs requested by the result). - The response organizes the duty scenario: returned duty layers, Chapter 99 codes in ACE reporting order, MPF and HMF fields, citations, conditional-input warnings, and advisory AD/CVD signals. It is decision support, not a completed entry or a final AD/CVD determination.
- Map it into an existing workflow. The worked example shows the request and response shape and maps selected fields into a fictional line record. Implementation time depends on your system, review controls, and entry-data model.
The endpoint
One public endpoint with GET and POST methods, one scenario 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. Use that calculator UI for manual spot checks; do not treat its browser route as a production integration contract.
Free access and fair use
Tandom API keys and the public duty-calculation endpoints are free to use, with fair-use safeguards by account and operation. Inspect the response rate-limit metadata and handle 429 with backoff rather than hard-coding a monthly quota. Create a key at tandom.ai/account/api-keys.
Retries
GET calculations are safe to retry after transient network, 429, or 5xx failures once the returned backoff period has elapsed. If you use the public POST variant for structured valuation inputs, follow the current API reference for idempotency and retry behavior.
Request shape
This guide uses the GET method, where inputs are query parameters. Only hts is strictly required; everything else is optional but gates which layers and fees apply. Use the documented POST method when you need structured valuation inputs.
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: country-of-melt-and-pour input (ISO 2) used by the current calculator where Section 232 treatment depends on metal origin.- Claim flags such as
uscastare booleans for specific importer certifications, not a general country field. Send them only when the importer has the supporting facts; otherwise surface the corresponding missing-input warning for review.
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[]: advisory matches against the active AD/CVD order list. Treat any returned case number or match type as a screening signal. UseGET /v1/adcvd/checkwith a detailed product description and supplier facts for deeper review.matchedExclusions[]: Chapter 99 exclusions the engine surfaced for the importer to consider. Each hasactionable,conditionStatus, anddisplayDescription. These are candidate signals; the importer must confirm eligibility and satisfy any claim conditions.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 a dated example snapshot from May 5, 2026, included to show the response shape. Re-run the current calculator or API before relying on any rate or dollar amount. You can inspect the same inputs in the calculator UI at tariffs.tandom.ai/calculator for the same inputs.
3. The line in the calculator's visual identity
The example 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. Do not infer the scope outcome from that heading match; the importer and qualified reviewer must confirm the product facts, governing scope text, and applicable rate.
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;
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. Treat this as an example mapping into a review record, not as an ACE-ready entry or a contract guaranteeing every response field for every scenario. Generate types from the current API reference before implementing a production integration.
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 route supports the calculator UI and is not the documented, versioned integration contract.
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.
Guessing metal-origin or certification inputs
Do not turn an importer-certification flag such as uscast into a country string or default it to true. Send a claim only when the importer has supporting facts. Otherwise, preserve the missing-input warning and route the scenario for review.
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 tandom.ai/account/api-keys. API access is free with fair-use and operation-specific safeguards; use returned rate-limit metadata rather than a hard-coded quota. - dutyInfo
- The top-level object returned by the API. Contains the rates, dollar amounts, layers, advisory AD/CVD cases, fees, and conditional-input flags produced for the queried scenario.
- 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) describes the match granularity. Heading and advisory matches are candidates to investigate, not confirmed assessments.
- matchedExclusions
- Array on the response carrying Chapter 99 exclusions the importer might investigate. The flags describe engine-level conditions; the importer must confirm eligibility and evidence before making a claim.
- 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.