Cross-border compliance,
one defensible answer.
Eight endpoints turn a free-text product description into a complete customs pack — tariff code, legally required certifications, contractually expected standards, dual-use flags, the duty rate and its provenance, the landed cost, and EAEU parallel-import recognition. Built for industrial importers landing equipment in Kazakhstan, Russia, Belarus, Armenia, Kyrgyzstan — and their counterparts in the EU and beyond.
The compliance workflow
Most importers stitch this workflow together by emailing 10–15 brokers and picking the most conservative answer. With LegalData you wire it into one call — or eight, if you want to surface each step in your own UI.
GET /v2/customs/lookupFree-text product description (any language) → top HS / TN VED candidates with confidence + rationale.
GET /v2/customs/obligationsFor each candidate code, fetch the legal and contractually-expected obligations, split by buyer context (gov / private / oil_gas / …).
GET /v2/customs/dual-use-checkCross-check Wassenaar + EU 2021/821 before shipment — this is what blocks the 3-week spectrometer scenario.
GET /v2/customs/alternativesNet-new: defensible alternative classifications ranked by feasibility × defensibility, with naming guidance. Carries advisory_disclaimer.
GET /v2/customs/dutiesDuty, VAT and excise for a code in a destination — with the provenance of the rate: how it was derived, where it was read, how much we trust it.
POST /v2/customs/landed-costCIF + duty + excise + the destination tax stack, each tax assessed on its own basis. Says so in the number when a component could not be priced.
POST /v2/compliance/checkRun all of the above in one round-trip — built to be wired as a single MCP tool call. Quota cost = 5 units.
GET /v2/eaeu/parallel-importIf you land in one EAEU member state, check which of the other four (RU, BY, AM, KG) recognize the certificate automatically.
Importing a centrifugal pump from France to Kazakhstan
Ethan's team imports oil & gas equipment for a Kazakh government tender. Here is the exact flow they would run.
1. Match the product to a TN VED code
/v2/customs/lookup accepts the description in any of 11+ supported languages. We embed it, KNN against the local nomenclature (TN VED in Kazakhstan) and rerank the top results with Haiku.
2. List obligations for the chosen code
Pass the top candidate to /v2/customs/obligations and we return two buckets — legally required and contractually expected — filtered by ?context=government.
3. Cross-check dual-use
A centrifugal pump is not on the Wassenaar list — but if the equipment were a mass spectrometer, this is the call that prevents three weeks of customs hold.
4. Or — one composite call
POST /v2/compliance/check runs all of the above plus parallel-import in one round-trip. Built for a single MCP tool call.
5. Same thing — from Claude Desktop
Install the MCP server and prompt Claude directly.
Multilingual product descriptions
You don't need to translate before calling /v2/customs/lookup. We auto-detect the input language (Cyrillic, CJK, Latin script heuristics) and translate to English internally before embedding. Pass ?lang= if you want to force a specific input language.
Supported: RU · ZH · JA · KO · EN · FR · DE · ES · IT · NL · PT. The title field in the response is keyed by ISO-639-1 code and ships en at minimum.
Dual-use detection (the "spectrometer" case)
A mass spectrometer arrived in customs without an export licence and sat for three weeks. The team had emailed 12 brokers and nobody flagged Wassenaar 3A201. That is the single most expensive failure mode in the workflow — and it's the one this endpoint eliminates.
/v2/customs/dual-use-check cross-checks the Wassenaar Arrangement and EU 2021/821 (with EAR / MTCR / NSG / Australia Group coverage rolling out) and surfaces the matching category, the underlying regulation, and country-level overlays (embargoes, additional national approvals).
Government vs private — GOST is not always mandatory
GOST standards aren't enforced at the EAEU customs line, but government and oil & gas tenders almost always require them in the contract. Pass ?context=government on /v2/customs/obligations and we split the response into two buckets:
- legally_required — enforced by customs (EAC, TR CU, import licences, dual-use authorisations).
- contractually_expected — buyer-side standards (GOST, EN, ISO) typical of the requested context. Comes with a rationale so you can justify the inclusion to a tender team.
Supported contexts: government · private · defense · oil_gas · mining · aviation · retail · healthcare.
Lighter codes, same legality
Sometimes two codes can both apply, and the alternative carries fewer obligations or a lower import duty. We surface those, ranked by feasibility × defensibility, with a naming suggestion that stays factual. Every response carries an advisory_disclaimer— final classification is the importer's legal responsibility.
One certificate, five countries
Under the Treaty on the EAEU, a certificate validated in one member state (KZ, RU, BY, AM, KG) is generally recognized in the other four. This is the rule that lets you land in Kazakhstan and resell into Russia without re-certifying.
Read the provenance before you quote a rate
A duty rate with no stated origin is a number you cannot defend in front of a customs authority. Since 1 September 2026, every row that /v2/customs/duties serves states how it was derived and where it was read. Four fields carry it.
rate_basis — how the rate was reached
national_line is a tariff line published by the destination itself. bloc_common_tariff is the customs union's common external tariff. bloc_default is a union-wide fallback — not a national line, and it should never be presented as one. derived means computed rather than read.
source_tier — where it was read
official is the administration's own publication. mirror is a second administration republishing it — our EU rows are read from the UK's Northern Ireland service, not from TARIC, and they say so. commercial_portal is a paid aggregator, which is how the five EAEU schedules reach us. derived is computed.
confidence and verified_at
Confidence runs from 0.50 to 1.00 in production; a 0.50 row means the rate could not be confirmed at an official source. verified_at is null on every duty row today — the provenance labels shipped ahead of the verification dates. Read a null there as “not re-checked”, never as “fresh”. source_url is populated on 42% of rows.
resolution — what was substituted for what you asked
A member state with no schedule of its own resolves against its customs union, and resolution names the substitution and nulls the national fields a bloc row cannot carry — a common external tariff sets duty, not the member state's VAT. When a 6-digit query's national child lines disagree, duty_pct is withheld and duty_pct_min / duty_pct_max carry the range instead. Heading 841370 genuinely spans 0%–1.7%, and returning the first child line would be a sample presented as an answer.
Coverage — and where it stops
GET /v2/customs/coverage answers the first question a buyer asks: which countries do you actually have? It returns per-country row counts, chapters covered, code depth, and the mix of rate bases and source tiers behind them — so the answer can be checked rather than taken on trust.
Measured in production on 2 September 2026: 121,391 duty rows across 11 schedules (GB, EU, RU, KZ, BY, AM, KG, US, GCC, TR, JP), 110,845 preferential rates across 32 programmes for the US, and an import-tax stack for 44 countries. 33 countries are served through their bloc rather than a schedule of their own; the endpoint reports both numbers separately so a bloc rate is never counted as a national one.
There is not one national tariff line for India, Indonesia, Malaysia, Egypt, the Philippines, Nepal, Nigeria, Kenya, Tanzania or Uganda. Landed cost computes for all of them from their tax stack, and the six GCC states resolve duty through a labelled bloc approximation — but a bloc default is not a national line, and /v2/customs/coverage will tell you so before you build on it. Two rates are seeded flat where the real schedule is banded: India's IGST at 18% (the schedule uses 5/18/40) and Malaysia's SST at 10% (5/10). Both sit at confidence 0.50 for that reason.
GET /v2/customs/coveragePer-country row counts, chapters covered, code depth, and the mix of rate bases and source tiers behind them. Ask it before you promise a market to a customer.
The other directionPOST /v2/customs/reverse-classifyStart from a target code and get a defensible product description for it — the classification argument written out, not just the code.
Chapter to regulatory domainGET /v2/customs/chapter-familiesThe canonical mapping from all 96 HS chapters to the regulatory domains that govern them. Useful for routing a catalogue before you have codes.
Duty calculator
Look up the import duty, VAT, and excise rate for any HS / TN VED / CN8 / HTS code, by destination country. Backed by the EAEU Common Customs Tariff (KZ, RU, BY, AM, KG), EU TARIC, and US HTSUS. Calls GET /v2/customs/duties through the playground proxy — sign in first, then attach an API key.
Example: code=8413701000, country=KZ — centrifugal oil pumps, duty-free under EAEU CCT + 12% VAT (the Ethan workflow baseline).
Tariff-rate history
Rates change. To see how a code's duty / VAT / excise moved over time, call GET /v2/customs/rates/history?code=&country=&since= for one code, or GET /v2/customs/rates/by-year?country=&year= for everything that changed in a country in a given year. Both return dated RateChange rows and carry the same advisory disclaimer as the duty lookup — confirm current rates with a licensed broker or the competent customs authority before relying on them.
curl -H "Authorization: Bearer ld_live_…" \ "https://api.legaldata.cleolabs.co/v2/customs/rates/history?code=8413&country=KZ&since=2024-01-01"
See the customs reference for the full response schema.