Changelog
V.v1Bulk lookups now include the Section 301 forced-labour duty
Bulk lookups (POST /api/v1/tariffs/resolve_batch) were leaving the Section 301 forced-labour duty out of the result. Single lookups (GET /api/v1/tariffs/resolve) were always correct, so only the batch endpoint was affected.
Any bulk lookup for goods originating in one of the 86 economies covered by that duty came back too low, by 10 or 12.5 percentage points depending on origin, between 24 July 2026 and 7 August 2026. Bulk and single lookups now return identical results.
If you priced shipments from bulk results in that window, re-run them. The corrected duty is higher, not lower, so entries filed on the old figures may be short.
rate_conditional, for rates that depend on a fact only your entry knows
Some rates cannot be settled from an HTS code, an origin and a date. Until now we picked a branch and served it at confidence 1.0 with no warning, which is the shape nobody thinks to report. /resolve and /resolve_batch now return a rate_conditional block on those lanes, naming what the answer turns on and what the other branch would be.
The live case is the ad valorem equivalent. Where a Section 301 floor or cap sits on a column 1 rate that is not ad valorem, U.S. note 52(k) makes the test a percentage of your entry's declared customs value, so two shipments of the same code at different values fall on opposite sides of the floor. Those lanes return reason: "ad_valorem_equivalent" with an AD_VALOREM_EQUIVALENT_REQUIRED warning and confidence 0.8. This is not a rare corner: 8.3% of general duties carry a per-unit term.
Read the bound before you total anything. The number on those lanes has not changed and it is a ceiling, not a certainty: where column 1 is a specific rate we net out nothing and charge the full floor, which over-states the duty. It is now marked rate_is_bound: "upper" instead of being served silently at full confidence. If you read only applicable_ad_valorem_rate you get exactly what you got before. Letting you pass the entered value on the request so we can compute the equivalent is the real fix and it is not built yet; tell us if you need it and it moves up.
The confidence reduction is its own named factor, rate_conditional, sitting beside base_rate rather than folded into the score, so you can see what cost you the 0.2.
A second reason, tariff_rate_quota, uses the same structure for in-quota versus over-quota. No lane emits it today. The textile and apparel quotas of the July 2026 forced-labor memo are directed but not established: USTR's own notice says it will establish them "when feasible", no Chapter 99 quota heading exists, and Bangladesh, Cambodia, Indonesia and Malaysia correctly carry the flat rate. Publishing in-quota alternatives now would headline a zero percent no importer can claim, so those four origins instead carry an info-severity TARIFF_RATE_QUOTA_DIRECTED warning with no confidence reduction. It flags a lane likely to move, not a doubt about today's number.
Additive. rate_conditional is always present and is null on almost every lane, and no existing field changed shape or meaning.
additional_measures now return effective_from
We have always returned effective_date_unknown, a boolean saying whether we know when a measure took effect, without ever returning the date itself. If we told you we knew, you still had to email and ask what the date was. Every entry in additional_measures now carries effective_from alongside the existing effective_to.
The field is null when the date is genuinely unknown. We do hold an internal fallback date for rows whose notice we could not parse, and we deliberately do not return it, because presenting an ingestion artefact as a legal effective date is worse than saying we do not know.
This also fills four keys the MCP tools and the AI chat have been promising and serving as null since launch: effective_dates.from, effective_dates.to, country_scope and applicable_countries. Those now carry real values.
Additive. No existing field changed shape or meaning.
USMCA relief on forced-labor duties is surfaced as a claim, and batch now matches single
U.S. note 52(g) and (h), headings 9903.05.93 (Canada) and 9903.05.94 (Mexico): the forced-labor duty does not apply to goods entered free of duty under the USMCA. Whether a given shipment qualifies is a per-shipment claim, and we cannot assert it from an HTS code, an origin and a date alone. So we charge the duty by default and return the heading in available_exemption on the measure. If your entry qualifies and you file that heading, you pay nothing on that leg. Erring high is the right default when we cannot prove the relief.
Three outcomes, all pinned in our regression corpus. A USMCA base rate already served means we assumed the claim, and the measure is suppressed. Canada or Mexico origin without a USMCA base means the duty is charged with the exemption surfaced. Any other origin is charged with no exemption offered.
Separately, a parity bug worth calling out. /api/v1/tariffs/resolve_batch was dropping available_exemption entirely, both for these carve-out lanes and for the described-article Section 301 exclusions under 9903.88.69, even though the batch response has always documented the field. The same HTS code and origin answered differently depending on which endpoint you called, and batch clients saw a duty applied with no indication that relief was claimable. Batch and single now agree.
Section 301 forced-labor duty now applies to EU member states
The EU leg of the Section 301 forced-labor action (headings 9903.05.38 and 9903.05.39, U.S. note 52) was keyed internally to the country code "EU". That is not an ISO 3166-1 country and it is never what an importer declares as origin, so the duty never fired for any of the 27 member states. Every EU line with a column 1 rate under 10% was returned base-only, at full confidence, with no warning attached.
This ran from the start of the regime on 2026-07-24 until 2026-07-31. If you priced EU-origin goods in that window, re-run them. Japan, South Korea, Switzerland and Taiwan carry real ISO codes and were correct throughout.
The note 52(j)(2) exemption list (9903.05.97) was keyed the same way. It has been re-keyed together with the charge, so the duty and its exemption stay in step rather than one applying without the other.
Section 232 patented pharmaceuticals (9903.04.60 through .69)
Proclamation 11020 put a Section 232 action on patented and branded pharmaceuticals, covering 149 provisions across chapters 29 and 30. We now model the whole family. It returns under program section_232 with program_detail pharmaceuticals.
The arithmetic is not uniform across the family, and that is the part worth knowing before you total anything yourself. 9903.04.60 (100%) and 9903.04.62 (15%) are all-in: the total is the higher of the Section 232 rate and the column 1 rate, not the sum. 9903.04.64 (plus 20%) is additive: the total is column 1 plus the rate. The remaining headings in the family levy nothing.
The United Kingdom lane, 9903.04.63, is one of those. It was set at plus 10% by the proclamation, then reduced to 0% before any entry could be charged: Commerce reduced it effective 12:01am Eastern on July 31, 2026 (FR Doc. 2026-15799, announced in CBP CSMS #69415934), which is the same moment the action's earliest lane opened. UK-origin goods on covered provisions therefore pay their column 1 rate and nothing more. The heading is still reportable on the entry, so we return it as a 0% advisory rather than dropping it, and you should still declare it.
Relief under this action is surfaced as a claim rather than applied silently. Where a heading offers relief you have to qualify for and file, we keep charging the duty and return the heading in available_exemption on the measure, so you can see it and claim it rather than discover it at entry.
Section 301 product exclusions (9903.88.69) are now claim-conditional
Many Section 301 List 3 lines carry a USTR product exclusion under 9903.88.69 (note 20(vvv)) that applies to a specific product description, not the entire statistical line. We had been treating these as line-wide waivers and returning 0%, which under-charged any importer whose goods did not match the narrow description.
The default rate on these lines now returns the full List 3 duty. For example, 8473.30.11.80 from China now returns 25%. The exclusion is surfaced explicitly as an available_exemption object on the section_301 measure; it names 9903.88.69 and states that the duty applies unless the entry qualifies and the exclusion is claimed with documentation.
If your goods match the excluded description, you still pay 0% by filing 9903.88.69 at entry, so your effective duty is unchanged. If your integration reads the flat applicable_ad_valorem_rate, note that it now reflects the line default; read available_exemption to see the exclusion you may claim.
Lines that are bare statistical numbers with no product description remain fully excluded and continue to return 0%.
Section 301 rates corrected for 9401.71.00 seating from China
Heading 9401.71.00 (seats with metal frames, upholstered) is split across Section 301 lists at the 10-digit statistical suffix, and we had been serving a single 7.5% rate across every suffix. That was wrong in both directions, and both are now corrected.
Three suffixes are on neither list and now correctly carry no Section 301 list duty: 9401.71.00.01 (highchairs and booster seats), .05 (infant walkers), and .06 (bouncers). These were previously over-charged at 7.5%.
Four lanes are on List 3 and now correctly carry 25% under 9903.88.04: 9401.71.00.08 (activity centers), .11 (other household), .31 (other), and the bare 8-digit heading. These were previously under-charged at 7.5%. 9401.71.00.07 (children's swings) remains on List 4A at 7.5%.
For China origin, the Section 301 forced-labor duty (9903.05.31, +12.5%) stacks on top where it applies. The correction also applies to past as_of dates, so a historical lookup resolves to the same corrected answer.
Forced-labor exemptions resolve, including the USMCA and CAFTA-DR lanes
The forced-labor action carries a large exemption structure, and applying the duty without
it would over-charge every exempted line across all 60 economies. All of it now resolves,
and the response names the exempting heading in `suppressed_measures` so you can show a
user why a duty did not apply.
* 2,120 product exemptions applying to every economy (U.S. note 52(b) to (e)):
`9903.05.86` general, `9903.05.88` civil aircraft, `9903.05.89` pharmaceutical.
* Thirteen economy-specific lists (`9903.05.96` to `9903.06.21`), for the United
Kingdom, the European Union, Switzerland, Malaysia, Cambodia, Guatemala, El Salvador,
Argentina, Bangladesh, Taiwan, Indonesia, Ecuador and Jordan.
* Goods already subject to Section 232 are excluded under `9903.05.90`.
* USMCA duty-free goods of Canada and Mexico, under `9903.05.93` and `9903.05.94`.
* CAFTA-DR textile and apparel goods, under `9903.05.95`.
The FTA lanes resolve only where the base rate for that same request came from the
agreement in question, so the exemption follows the preference claim rather than the
origin alone.
Some exemptions cannot be resolved from an HTS code. Goods in transit, donations,
informational materials and a set of narrowly described articles depend on a per-entry
claim or describe something narrower than the subheading they sit in. Those still return
the duty, because suppressing them would under-state every other good in the same
subheading.
Brazil now returns both of its Section 301 actions
Brazilian origins are covered by two separate Section 301 actions, and the response now
carries both:
* `9903.05.01`, +25%, the country action under U.S. note 50, effective 22 July 2026
* `9903.05.27`, +12.5%, the forced-labor action under U.S. note 52, effective 24 July 2026
`6404.11.90` / BR now returns 57.5% (20% base + 25% + 12.5%).
The two are separate investigations. Neither heading's exception list names the other,
the notice carries no non-cumulation language, and the Presidential memorandum states
that "each tariff action directed in this memorandum is separate from every other." We
therefore read them as stacking. None of the primary sources addresses the interaction
directly, so if your broker reads it differently we would like to hear it.
This also closes a regression in which the note 50 duty disappeared after the 23 July
sync. Two independent causes: a scrape arrives as six chunked uploads and only one
carries Chapter 99 data, but all six were running the stale-capping step, so survival
depended on which job finished last; and a guard added on 23 July required a note body
that the USITC feed does not export. Both are fixed, and the duty is now declared in a
way the feed cannot silently drop.
Section 301 forced-labor duties now returned for all 60 economies
The Section 301 forced-labor action, effective 24 July 2026, was not being returned. Any
covered origin resolved without it, at full confidence and with no warning, so the answer
was understated by 10 to 12.5 points with nothing to signal it.
All 60 economies now resolve, under headings `9903.05.20` through `9903.05.84`.
* `6909.19.50` / CN → 41.5% (base 4% + 25% List 3 + 12.5%)
* `6909.19.50` / IN → 14% (base 4% + 10%)
* `9503.00.00` / JP → 12.5%
Two rate structures, and they are not interchangeable. Most economies are additive.
Five are not: the European Union, Japan, Korea, Switzerland and Taiwan use a combined cap,
where the answer is the higher of the threshold or the Column 1 rate, not the sum. The
thresholds differ, and the EU and Taiwan cap at 10% rather than 12.5%. The Presidential
memorandum behind the action puts it directly: the tariff is imposed "so that the sum of
the MFN tariff and the tariff imposed pursuant to these investigations shall be 12.5
percent, and where such product's MFN tariff is greater than or equal to 12.5 percent,
the Trade Representative shall impose a section 301 tariff of zero."
Every economy's rate was checked line by line against that memorandum.
Sources: CBP CSMS #69326983, FR 2026-15181, FR 2026-15274.
Default pricing date is now US Eastern
When you call /resolve without an as_of, we now price for the current date in US Eastern time, matching how federal tariff actions state their effective times (12:01 a.m. eastern). Same-day changes, like a Section 122 sunset, now flip at midnight ET rather than midnight UTC. To price a specific shipment, pass as_of set to the entry date; note that a fixed past date always returns that day historical rates and will not reflect newer tariffs or sunsets.
Headings that state their rate in words now return a pointer
Some headings never state a number. They tell you to look somewhere else:
* `8206.00.00.00` (tool sets) — "the rate of duty applicable to that article in the set
subject to the highest rate of duty"
* `6103.22.00` (ensembles) — "the rate applicable to each garment in the ensemble if
separately entered"
* `9110.11.00.00` (watch movements) — "the rate applicable to the complete, assembled
movement"
* `9005.90.40.00` (optical parts) — "the rate applicable to the article of which it is a
part or accessory"
These used to come back as `rate_type: "unparsed"`, with the sentence in `raw_rate` and
nothing a program could act on. They are now `rate_type: "reference"` and carry
`base_tariff.refers_to`, alongside a `BASE_RATE_REFERENCE` warning,
`summary.base_rate_known: false` and a confidence of 0.5.
Eight pointer kinds are now recognised:
* `set_highest` — the highest-rated article in the set
* `ensemble_component` — each garment, rated as if entered separately
* `parent_article` — the article of which this is a part or accessory
* `assembled_movement` — the complete, assembled movement
* `absence_of_heading` and `absence_of_subheading` — the rate that would apply without it
* `heading` — a named heading, for example `{"targets": ["2009"]}`
* `note` — a named U.S. note, for example `{"note": "U.S. note 3", "basis": "repair_value"}`
* `self_subheading` — this subheading's own rate, optionally with `delta_percentage`,
`plus_percentage`, or `originating_in` for a trade-agreement rate
We record the pointer. We do not follow it. Resolving "heading 2009" on your behalf would
mean choosing one of its children for you, which is a classification decision, not a rate
lookup.
88 duty rows moved from unparsed to reference. Four rows remain unparsed across the whole
schedule: the drawback clauses of `9801.00.70` and `9801.00.80`, which genuinely state no
rate.
Compound duties no longer drop a term, and 201 rates now parse
Two correctness fixes and a large coverage improvement.
A duty carrying both a percentage and a per-unit component was classified from a
denormalised `compound_rate` flag rather than from the components themselves. When a
backfill set the components without the flag, the rate was demoted to `specific` and its
ad valorem side was silently dropped. Thirteen headings were affected, including
`9606.21.40.00` (buttons), whose rate is 4.6% plus 0.3 cents per line per gross.
Separately, ten watch and clock headings had been storing only the first two terms of a
three-term rate, which under-reported their duty.
The rate parser gained coverage for rate shapes it had always refused:
* Chapter 99 special-column rates qualified by a trade program, such as "$1.41/kg (PA)".
* Missing unit aliases: per metre, per linear metre, per pack, per article, per jewel,
per line per gross, per thousand pins.
* Rates with three or more terms, and rates charged on a metal content, a component of the
article, the fair retail value or the cost of repairs.
* The polarimeter-degree sugar formulas of Chapter 17, including their floors.
* Cross-references to another heading, subheading or U.S. note.
* Damage introduced by the source extraction, such as a soft-hyphen break inside a word.
Duties whose rate text contains a number and which we could not parse fell from 203 to 2.
A further 90 headings state their rate in words alone, with no number at all; those are
covered in V.1.0.7.
Unreadable base duties are no longer reported as 0%
When a base duty could not be parsed, the API returned
`rate_type: "ad_valorem", percentage_component: null`, and `summary.notes` said
"Base duty is 0%" alongside `confidence.score: 1.0` and an empty `warnings` array. Any
client that reads a null percentage as zero computed a duty-free base. Our own AI tools
rendered those headings as "Free".
This affected 91 general-column headings, including beet sugar, and the lead, molybdenum,
tungsten and magnesium ores.
A base duty that is not a plain number now says so, on every endpoint including
`resolve_batch`:
* `summary.base_rate_known: false` — `applicable_ad_valorem_rate` excludes the base duty
and is a lower bound.
* `warnings[]` carries one of `BASE_RATE_UNPARSED` (we could not read it),
`BASE_RATE_NOT_CALCULABLE` (we read it but it needs inputs only you hold) or
`BASE_RATE_REFERENCE` (it defers to another heading or note), each with the source text
and, where relevant, `inputs_required` or `refers_to`.
* `confidence` gains a fourth factor, `base_rate`, which drops the score to 0.5.
A specific duty is no longer described as "Base duty is 0%" either. A rate of 68 cents per
head has no ad valorem component, but it is not a zero duty, and the note now says so.
`applicable_ad_valorem_rate` is unchanged in shape and remains a number.
New rate_type values: unparsed, reference and formula
`base_tariff.rate_type` previously returned only `ad_valorem`, `specific` or `compound`.
Three values have been added, and each one replaces a case that used to be reported
misleadingly as `ad_valorem` with a null rate:
* `formula` — we read the rate but cannot total it without inputs only you hold. Carries
`components[]`, `constraints[]`, `inputs_required` and `raw_rate`.
* `reference` — the heading defers to another heading, subheading or U.S. note. Carries
`refers_to` and `raw_rate`.
* `unparsed` — we could not read the rate at all. Carries the verbatim USITC text in
`raw_rate`.
If your integration switches on `rate_type`, add these three cases. A response with
`rate_type` of `formula`, `reference` or `unparsed` has a null `percentage_component`,
and a null percentage is not a zero percentage. A `formula` rate deliberately exposes no
scalar at all: for `1.7 cents/kg on lead content`, a `per_unit_component` of 0.017 would
invite you to multiply by total kilograms when the correct multiplicand is the lead
kilograms.
The API path is unchanged and remains `/api/v1`.
Structured rate components on base_tariff
Some US HTS headings carry a base duty that a single number cannot express. A watch
movement is dutiable at "36 cents each + 5.6% + 2 cents per jewel". Lead ore is dutiable
per kilogram of lead content, not per kilogram of ore. Beet sugar is dutiable per
kilogram, reduced for every polarimeter degree under 100, with a floor. Until now the API
had only two fields for a rate, `percentage_component` and `per_unit_component`, so these
headings had no honest representation.
`base_tariff` now carries five new fields:
* `components[]` — every term of the rate, as `{kind, value, unit, basis}`. `kind` is
`specific` or `ad_valorem`. `basis` appears only when the term applies to something
other than the entered value or the entered quantity, for example `lead_content`,
`battery`, `fair_retail_value` or `degree_under_100`.
* `constraints[]` — floors and caps, for example `{"kind":"floor","value":0.03143854,"unit":"kg"}`
or `{"kind":"cap","basis":"complete_movement"}`.
* `duty_calculable` — `false` when the duty cannot be totalled from entered value and
entered quantity alone.
* `inputs_required` — what you must supply when `duty_calculable` is `false`, for example
`[{"name":"polarimeter_degrees","unit":"degree"}]`.
* `refers_to` — for headings that name another heading or a U.S. note instead of stating a
rate, for example `{"kind":"heading","targets":["2009"]}`.
These fields are additive. `percentage_component` and `per_unit_component` keep their exact
meaning for ordinary ad valorem, specific and compound rates.
We describe these rates. We do not total them, and we do not follow a reference to another
heading. The jewel count of a movement, the copper content of an ore and the polarimeter
reading of a sugar shipment are facts only the importer holds. Guessing at them would
produce a confident wrong number.
AI Chat Assistant
We have added a new way to discover HTS codes, our AI agent trained on our entire database will be able to help you discover and understand HTS codes.
Organizations: Collaborate on HTS codes with your team
Invite and collaborate on hts codes by inviting your team to your TariffsAPI organization. Share your subscription API and AI credits
Tariff Data Fixes: 8471 Exemption Restored & Cleaner API
Section 122 exemption restored for heading 8471, exempted measures now visible with suppression reasons, and duplicate duties removed.
New API Dashboard
We have refreshed the API dashboard to include more details around API credits, API keys usage and billing.
HTS source on API response
We have added the official source on every API response. You can now always verify the official sources by yourself for every API call.
Better Data Accuracy
We have increased our audit process to increase the data accuracy, including unique cases like the reversal of teh IEEPA tariffs under the current administration.
MCP server connection
Speak directly to TariffsAPI database through our MCP endpoint 🔥
Batch resolve endpoint
You can now query several rates at a time, up to 150 rates in one API call. This is perfect if you have high needs and product interations.
Country-Aware Tariff Resolution (Section 301, Trade Agreements & HTS Inheritance)
“Given an HTS code and a country of origin, what tariffs apply today?” - Try out the /resolve endpoint and our updated tariffs calculator.
Automatic inheritance when exact matches aren't found
querying 8541.10.00.80 will return tariffs from 8541.10.00 when the child has no tariffs.