Guides · Customs & Compliance

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.

Workflow

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.

End-to-end example

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.

curl
curl -H "Authorization: Bearer ld_live_…" \
  "https://api.legaldata.cleolabs.co/v2/customs/lookup?description=Pompe%20centrifuge%20pour%20p%C3%A9trole%20brut&country=KZ&lang=fr&top=3"
response
{
  "data": {
    "query": "Pompe centrifuge pour pétrole brut",
    "detected_lang": "fr",
    "candidates": [
      {
        "code": "8413701000",
        "system": "tn_ved",
        "country": "KZ",
        "hs6_parent": "841370",
        "title": {
          "fr": "Pompes centrifuges pour liquides pétroliers",
          "en": "Centrifugal pumps for petroleum liquids",
          "ru": "Насосы центробежные для нефтяных жидкостей"
        },
        "confidence": 0.92,
        "rationale": "Pompes centrifuges pour liquides — usage pétrole couvert par la sous-position 8413.70.10.00."
      },
      { "code": "8413810000", "system": "tn_ved", "country": "KZ", "confidence": 0.71, "title": { "en": "Other pumps for liquids" }, "rationale": "Catégorie résiduelle 'autres pompes' — moins spécifique au pétrole." }
    ]
  }
}

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.

curl
curl -H "Authorization: Bearer ld_live_…" \
  "https://api.legaldata.cleolabs.co/v2/customs/obligations?code=8413701000&country=KZ&context=government&lang=en"
response
{
  "data": {
    "code": "8413701000",
    "country": "KZ",
    "context": "government",
    "legally_required": [
      {
        "type": "certification",
        "title": "EAC Declaration of Conformity",
        "regulation": "TR CU 010/2011",
        "source": "eurasian_economic_commission",
        "mandatory": true,
        "evidence_required": ["test report from accredited lab", "manufacturer declaration"]
      }
    ],
    "contractually_expected": [
      {
        "type": "standard",
        "title": "GOST 17398-72 — Pumps. Terms and definitions",
        "source": "gost",
        "mandatory": false,
        "rationale": "Standard frequently required by KZ government buyers in oil & gas tenders."
      }
    ],
    "dual_use_flag": { "applicable": false, "checked": true }
  }
}

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.

curl
curl -H "Authorization: Bearer ld_live_…" \
  "https://api.legaldata.cleolabs.co/v2/customs/dual-use-check?description=mass%20spectrometer%20industrial&destination=KZ"
response
{
  "data": {
    "applicable": true,
    "confidence": 0.94,
    "regimes": [
      { "regime": "wassenaar",  "category": "3A201.a — Mass spectrometers", "entry_id": "wassenaar-3A201-a" },
      { "regime": "eu_dual_use","category": "3A201.a",                     "annex": "Annex I", "regulation": "Regulation (EU) 2021/821" }
    ],
    "destination_risk": {
      "country": "KZ",
      "embargoed": false,
      "additional_approvals_required": ["KGB Republic of Kazakhstan export control approval"]
    },
    "recommendation": "Verify with national export control authority before shipment. Allow 4–8 weeks for approval."
  }
}

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.

typescript
import { LegalData } from '@cleo-legal/sdk';

const ld = new LegalData({ apiKey: process.env.CLEO_LEGAL_API_KEY! });

const pack = await ld.compliance.check({
  product: {
    description: 'Pompe centrifuge pour pétrole brut',
    lang: 'fr',
    intended_use: 'oil pipeline transfer',
  },
  origin_country: 'FR',
  destination_country: 'KZ',
  client_type: 'government',
  options: { include_alternatives: true, include_dual_use_check: true, include_parallel_import: true },
});

console.log(pack.data.product_classification.primary_code.code);
//   → '8413701000'
console.log(pack.advisory_disclaimer);
//   ↑ ALWAYS surface this verbatim to your end user

5. Same thing — from Claude Desktop

Install the MCP server and prompt Claude directly.

prompt
> Find the customs obligations for a centrifugal pump exported from France
> to Kazakhstan for a government oil & gas client. Flag any dual-use risk
> and tell me which EAEU countries would recognise the EAC certificate.

Claude will call:
1. compliance_check({ product: …, origin_country: 'FR',
                       destination_country: 'KZ', client_type: 'government' })
2. (and surface the advisory_disclaimer verbatim before the recommendation)
Languages

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.

curl · Russian input
curl -H "Authorization: Bearer ld_live_…" \
  "https://api.legaldata.cleolabs.co/v2/customs/lookup?description=%D0%9D%D0%B0%D1%81%D0%BE%D1%81%20%D1%86%D0%B5%D0%BD%D1%82%D1%80%D0%BE%D0%B1%D0%B5%D0%B6%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B5%D1%84%D1%82%D0%B8&country=KZ&top=3"

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.

Risk · #1 blocker

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).

response
{
  "data": {
    "applicable": true,
    "confidence": 0.94,
    "regimes": [
      { "regime": "wassenaar",  "category": "3A201.a — Mass spectrometers", "entry_id": "wassenaar-3A201-a" },
      { "regime": "eu_dual_use","category": "3A201.a",                     "annex": "Annex I", "regulation": "Regulation (EU) 2021/821" }
    ],
    "destination_risk": {
      "country": "KZ",
      "embargoed": false,
      "additional_approvals_required": ["KGB Republic of Kazakhstan export control approval"]
    },
    "recommendation": "Verify with national export control authority before shipment. Allow 4–8 weeks for approval."
  }
}
Buyer context

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.

Defensible alternatives

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.

curl
curl -H "Authorization: Bearer ld_live_…" \
  "https://api.legaldata.cleolabs.co/v2/customs/alternatives?code=8413701000&country=KZ&optimize=both"
response
{
  "data": {
    "original": { "code": "8413701000", "title": "Centrifugal pumps for oil", "estimated_obligation_count": 6, "import_duty_pct": 5.0 },
    "alternatives": [
      {
        "code": "8413810000",
        "title": "Other pumps for liquids",
        "delta_obligations": -2,
        "delta_duty_pct": -2.5,
        "naming_suggestion": "Industrial liquid pump (general purpose)",
        "feasibility_score": 0.78,
        "defensibility_score": 0.85,
        "rationale": "Generic 'other pumps' avoids the oil-classification that triggers TR CU 032 (equipment for explosive environments). 'Industrial liquid pump' is factual and defensible if the product is not exclusively used for oil."
      }
    ]
  },
  "advisory_disclaimer": "These suggestions are informational only. Final customs classification must be validated by a licensed broker or competent authority. Misclassification carries legal liability."
}
EAEU

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.

Issued in
KZ
Recognised in
RU
Recognised in
BY
Recognised in
AM
Recognised in
KG
curl
curl -H "Authorization: Bearer ld_live_…" \
  "https://api.legaldata.cleolabs.co/v2/eaeu/parallel-import?cert_country=KZ&cert_type=EAC&product_code=8413701000"
response
{
  "data": {
    "originating_country": "KZ",
    "valid_in": ["RU", "BY", "AM", "KG"],
    "regulation_basis": "Treaty on the EAEU, Article 53",
    "exceptions": ["defense-grade equipment requires per-country approval"],
    "notes": "EAC declarations are recognized across all 5 EAEU members."
  }
}
Provenance

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

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.

What we do not have

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.

Live demo

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.

Live demo · production APISign in to the playground first to attach an API key — the call is then proxied securely.

Example: code=8413701000, country=KZ — centrifugal oil pumps, duty-free under EAEU CCT + 12% VAT (the Ethan workflow baseline).

Time series

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.

Required notice

Advisory disclaimer

Get started

Run a real check in 2 minutes