US Tariff and Customs Intelligence API
What it costs to bring anything into the United States — every duty rate in all 98 chapters of the Harmonized Tariff Schedule, parsed into numbers you can compute with. Free, public domain, no key.
Tariff lines from the live USITC schedule, 19,949 of them classifiable, across 98 chapters
Of 68,719 published rate strings parse into computable terms — the rest are reported, never priced
CBP ports of entry in 46 districts, with the codes an entry is filed against
Countries with their trade-programme eligibility, from Schedule C
What this is
A duty calculator and reference layer for US imports of any kind: the live USITC Harmonized Tariff Schedule in full, the trade-programme eligibility that decides whether a special rate is even available, the CBP ports an entry can be filed at, the Federal Register notices behind a rate change, and the 19 CFR parts that govern how the entry is made. Give it a tariff line, a quantity in that line's own unit and a customs value, and it returns the statutory duty with every figure it used to get there.
Post a tariff line, a volume and a customs value; get back the duty in dollars, the
working that produced it, the effective ad-valorem rate it implies, any special
rate the country of origin is eligible for, and the chapter 99 headings you should check
yourself. Every figure is traceable to the HTS revision named in the response.
dutyUsd: null with the reason — never 0 — when a rate needs a
quantity it was not given, or is not arithmetic at all. dutyUsd: 0 only ever means
Free. Each refusal is stated in the response body, not just here.
Estimate a duty
Try the same shipment at a customs value of , and — the duty will not move, and the effective rate will. Then try 0102.29.40.24 at 5000 kg, or 1701.13.05.00 — a sliding-scale rate this service reads and refuses to price.
Press Estimate.Endpoints
| Route | What it answers |
|---|---|
GET /tariff/v1/tariffs | Search the schedule: q, chapter, heading, htsno, programme, rateKind, unit, classifiable. |
GET /tariff/v1/tariffs/{htsno} | One line. An ambiguous prefix is a 409 listing the candidates, never an arbitrary pick. |
GET /tariff/v1/tariffs/release | Which HTS revision is live, and which this service last recorded. |
POST /tariff/v1/duty-estimate | Duty on a shipment, with the arithmetic and the implied effective ad valorem rate — or a refusal naming the unit it needs. |
GET /tariff/v1/chapter99 | Additional-duty headings — listed to check, never auto-applied. |
GET /tariff/v1/countries | Schedule C codes, ISO codes and trade-programme participation. |
GET /tariff/v1/ports | CBP Schedule D districts and ports of entry. Entry codes — not a congestion feed. |
GET /tariff/v1/notices | Federal Register tariff actions. |
GET /tariff/v1/customs-rules | 19 CFR citations for entry, classification, liquidation and protest. |
GET /tariff/v1/changes | What moved between HTS revisions. ?dataset=hts&since= |
GET /tariff/v1/sources | Every upstream — and every source evaluated and rejected, with the reason. |
GET /tariff/api/v1/*.json | The complete datasets as static files. Free and cacheable. |
Quick start
# The statutory rate on a line
curl https://apis.allanninal.dev/tariff/v1/tariffs/8471.30.01
# What a shipment actually costs — quantity in the line's own unit
curl -X POST https://apis.allanninal.dev/tariff/v1/duty-estimate \
-H 'content-type: application/json' \
-d '{"htsno":"6402.19.50.31","quantity":240,"quantityUnit":"prs.","customsValueUsd":9000,"countryOfOrigin":"VN"}'
# Which lines this service will REFUSE to price, and why
curl 'https://apis.allanninal.dev/tariff/v1/tariffs?rateKind=sliding-scale&limit=3'
# One chapter at a time, as a cacheable file
curl https://apis.allanninal.dev/tariff/api/v1/chapters/84.json
The part that does not exist anywhere else
USITC publishes one Harmonized Tariff Schedule: the current one. It overwrites it with each revision and offers
no archive — /reststop/releases is a 404. So the answer to “what was the statutory duty on 14 March”
cannot be reconstructed after the fact.
This service records each release as it appears. /v1/changes?dataset=hts&since= reports what moved.
Ask for a date before retention began and you get a 422 naming the coverage start — it will never answer
with the current schedule dressed as history.
Four things this API will not do
Stack your section 301 duties
Chapter 99 headings define their scope in prose, not as a line list. Whether one reaches your goods is a classification judgement. They are returned as candidates to verify — never added into a total.
Tell you a shipment qualifies for an FTA rate
A programme letter means the country participates. Whether the goods qualify depends on rules of origin this service does not evaluate. The special rate is reported as conditional, with the saving it would represent.
Serve trade volumes or values
Those need a Census Bureau API key, and this service is built to need no key from anyone. The gap is declared at /v1/sources, not hidden behind an endpoint that does not exist.
Track a ship or a container
No vessel positions, no port congestion, no container tracking. Every “open” AIS feed wants a key, an account, or a receiver of your own; each is named in the rejection register. What is here is Schedule D — which ports exist and what their entry codes are.
Reference data (static, free, no auth)
The complete datasets as plain files — cacheable, no key, and the same data the API serves. Nothing is held back for a paid plan.
| File | Contents |
|---|---|
| index.json | Index of everything below |
| chapters/index.json | The complete current HTS revision, one file per chapter — the whole schedule is ~34 MB, so it is not served as one download · classifiable lines only |
| duty-rates.json | General, special and column-2 rates per line |
| chapter99.json | 75 additional-duty headings, as candidates to verify |
| countries.json | 241 countries and their trade-programme eligibility |
| tariff-notices.json | Federal Register notices behind rate changes |
| ports.json | 431 CBP ports of entry in 46 districts, with their Schedule D codes |
| customs-rules.json | The 19 CFR parts that govern the entry |
| sources.json | Every upstream used, and everything rejected with its reason |
| plans.json · versions.json | Plan catalogue and version manifest |
Access & rate limits
| Plan | Per minute | Per day | History | Price |
|---|---|---|---|---|
| Free (anonymous) | 60 | 5,000 | 30 days | $0 |
| Free + key | 300 | 50,000 | 30 days | $0 |
| Public interest | 1,200 | 500,000 | full | $0, by application |
| Pro not open yet | 1,200 | 500,000 | full | $19/mo · $190/yr |
| Business not open yet | 6,000 | 5,000,000 | full | $49/mo · $490/yr |
Every plan has every feature. Plans differ only in how much you may use, never in what you
may reach — nothing is behind a paywall. Limits are published on every response as
RateLimit-* headers. Pro-level access is free, on request, for journalists, academics
and nonprofits. The plan catalogue.
FAQ
Do I need an API key?
No. Every read path is anonymous and free at 60 requests a minute and 5,000 a day. A free key raises that to 300 a minute and 50,000 a day; it does not unlock anything, because every plan already reaches every endpoint and every field.
Why does the effective duty rate change when the value changes but the duty does not?
Because thousands of HTS lines carry specific rates — cents per kilogram, per litre, per pair, per dozen — which are not a percentage of value. A 1,000 litre wine shipment pays the same 63 dollars whether it is worth $500 or $40,000, which is 12.60% in one case and 0.16% in the other. If an estimate's effective rate ever stops moving with value, the estimator has broken.
Does this include import volumes or the duty actually collected?
No. Those come from the US Census Bureau's international trade API, which requires an API key, and this service is built to need none from anyone. The gap is declared at /v1/sources rather than hidden behind an endpoint that does not exist.
Why did I get dutyUsd null instead of a number?
Because the rate on that line is not arithmetic this service can do, and it will not invent one.
Either it needs a quantity you did not send — the answer names the unit, as
requiredUnit — or the published rate is a cross-reference to another article, a
sliding scale keyed to a measured property, a pointer into a schedule note, a conditional free
entry, or a duty apportioned across components priced separately. A 0 would look
like good news; the reason is the honest answer. dutyUsd: 0 only ever means Free.
Does it include port congestion or container tracking?
No. There is no keyless, registration-free source for either — AIS “open” feeds want a receiver you contribute or a paid key, and US port congestion is published per port as PDFs. What is here is CBP Schedule D: the districts and ports an entry is filed at, with their codes. Each rejected source is named at /v1/sources with what it would have provided.
Are section 301 and IEEPA duties included in the estimate?
No, and deliberately. Chapter 99 headings state their scope in prose rather than as a list of tariff lines, so whether one reaches a given shipment is a classification judgement this service cannot make for you. They are returned as candidates to verify, never summed into a total.
Cost
Free, no signup: 60 requests a minute, 5,000 a day, 30 days of history. A free key raises that to 300 a minute and 50,000 a day and unlocks nothing — every plan reaches every endpoint and every field. Plans differ only in how much you may use. Pro-level access is free, on request, for journalists, academics and nonprofits.
👋 Want an API for your idea?
This API was designed and built by Allan Niñal, an AI & software engineer who ships friendly, well-made, developer-first APIs and data products. Tell me the problem in plain words — trade, compliance, public data, or anything else — and I'll turn it into something that works.