Quick answer
UN Comtrade publishes international merchandise trade — who exported what to whom, by HS commodity code, by year — and the preview tier is free and keyless:
GET https://comtradeapi.un.org/public/v1/preview/C/A/HS
?reporterCode=842&period=2022&cmdCode=TOTAL&flowCode=X
That request works from your terminal in one line. Building something you can depend on against it is a different exercise, because the free tier has four constraints that are easy to miss and expensive to discover in production.
That is the UN Comtrade Trade Data Scraper. One flat row per (reporter, partner, commodity, flow, period).
The four constraints, in the order they bite 🚧
1. One period per call. Not a preference — the API rejects multi-period requests outright. A five-year series is five calls, and if you built your client assuming you could pass a range, you find out at integration time.
2. A hard 500-row cap with no pagination parameter. There is no offset, no cursor, no next. You get up to 500 rows and that is the end of the conversation. This is the dangerous one, because truncation looks exactly like completeness — a broad query returns 500 rows and nothing tells you there were 40,000. The only real answer is to narrow the query until you are under the cap, and to say so out loud when you might not be.
3. An unauthenticated 429 with no Retry-After. The throttle exists, it is undocumented in the response, and it gives you no hint how long to wait. So you pace requests deliberately and back off exponentially, rather than reading a header that never arrives.
4. Human-readable names are null on this tier. reporterISO, partnerISO and the descriptive text fields come back null — you get numeric codes. An earlier draft of our own docs showed them populated, which was aspirational rather than true. We replaced the example with real output and wrote the limitation into the FAQ, because a sample that does not match what the API returns is worse than no sample.
The wire format disagrees with the docs in at least one place 🔍
motCode — mode of transport — is documented as a string. The live API sends an integer. Our first smoke test failed on a ValidationError, which is the correct outcome: a typed model caught a wire-format assumption at build time rather than shipping rows with a silently coerced field.
This is the argument for validating every row against a real schema instead of passing dictionaries around. The failure is loud, it happens once, and it happens to us instead of to a customer.
One bad code should not kill the run 🛟
Ask for a reporter, partner and commodity combination that does not exist and you get nothing back for that combination. If your client treats that as fatal, one stale HS code in a list of two hundred destroys the whole run — and the customer still pays the start fee.
We skip the combination with a warning and keep going. This is the single most common defect shape we find in our own fleet: a recoverable error crashing the entire run instead of skipping one item. It is worth checking for first, every time.
What a row looks like
Reporter and partner codes, HS commodity code, flow direction, period, trade value, netweight, quantity and unit, plus the sparse fields — cifvalue, fobvalue, qty — kept as real nulls rather than dropped. A missing value is information; a missing column is a bug.
Verified live: reporterCodes=842, periods=2022, cmdCodes=TOTAL, flowCodes=X returns 223 real rows, checked both through a raw probe and an end-to-end run.
A note on TLS fingerprints, since we now check every host 🔐
We recently found an institutional API that returns 503 to every Chrome TLS fingerprint while serving Firefox and Safari fingerprints normally — the anti-blocking measure was the block signal. So we now probe each new target across profiles before committing to one.
Comtrade is clean: chrome131, chrome124, firefox133, firefox135, safari180 and no impersonation at all return identical 199,412-byte bodies. This host does not care who you look like. Worth knowing, and worth measuring rather than assuming in either direction.
Who this is for 🎯
- Trade finance — country-level import/export exposure.
- Supply chain and sourcing — alternate origin countries for a commodity.
- Export market research — who already buys what you sell, and from whom.
- Economists and policy analysts — bilateral flows by commodity over time.
The honest limitations 🚧
-
HSclassification only in v1.SITC/BECare untested and out of scope. -
partner2Code,customsCodeandmotCodeare output columns, never input filters. - 500 rows per call, no pagination. Narrow the query; we tell you when you are near it.
- A current snapshot, not a revision tracker — UN Comtrade restates past periods periodically.
Pricing
$0.20 per run plus $0.002 per row — $2.20 per 1,000. Pay for rows that land.
→ UN Comtrade Trade Data Scraper on Apify
Built by Devil Scrapes. We handle the one-period-per-call rule, the cap that hides truncation, the 429 with no header, and the field the docs got wrong.