Classify HS Code

Classify a single HS / Combined Nomenclature code against the EUDR's Annex I scope: is it covered, which commodity does it belong to, which Annex entry matched, and which horizontal exemptions could still take it back out. It also says whether TRACES accepts the code, what fm-api would send, and which supplementary unit TRACES expects.

This is global reference data — not org-scoped, so every org gets the same answer for the same code and scope version.

Request

Method & Path

GET /regulations/eudr/scope

Query parameters

ParameterRequiredDescription
hs_codeyesThe code to classify. 2–8 digits; separators (spaces, dots) are stripped before matching, so 4403, 44.03 and 44 03 are equivalent.
as_ofnoResolve the scope version in force on this date (ISO date). Defaults to today.
versionnoPin an explicit scope-version key, including drafts — for previewing a pending Delegated Act before it is adopted.

Response

data

FieldTypeDescription
hs_codestringThe submitted code, normalised to bare digits.
in_scopebooleanWhether the code falls within EUDR Annex I scope.
commoditystring | nullCanonical EUDR commodity (cattle, cocoa, coffee, palm, rubber, soy, timber), or null when out of scope or ambiguous.
matched_cn_codestring | nullThe Annex I CN code that matched, or null.
matched_byenumGranularity of the match — chapter, heading, subheading, tariff, broader, or none.
recognisedbooleantrue when the submitted code is itself an Annex I CN entry, in or out of scope. It does not mean TRACES accepts the code: read traces_accepted for that.
ex_headingbooleantrue when the matched entry carries an "ex" prefix: only part of the heading is in scope, so confirm the specific product.
annex_descriptionstring | nullAnnex I product description for the matched entry.
candidate_commoditiesstring[]Populated when a broad code overlaps more than one in-scope commodity — supply a more specific code to disambiguate.
applicable_exemptionsobject[]Horizontal (product-attribute) exemptions in force for this scope version. The list follows the version, not the submitted code, so the same codes come back for an out-of-scope answer. The version 2023-1115 carries three: packaging_single_use, recycled_100pct and waste; 2026-da carries eight. Each entry has code, label, description and legal_basis. The examples after the first abbreviate this list.
notesstring[]Human-readable caveats about the classification.
traces_acceptedboolean | nullWhether TRACES accepts the code, after any truncation to traces_wire_hs_code. null when out of scope.
traces_wire_hs_codestring | nullThe code fm-api would send TRACES (e.g. 010229 → 0102). null when out of scope.
traces_truncatedboolean | nulltrue when traces_wire_hs_code is a shorter accepted prefix than the code's own 6- or 8-digit form. null when out of scope.
accepted_hs_codesobject[]The codes TRACES accepts instead, each { code, description, label } (label is "<code> — <description>", ready to display). Present only when traces_accepted is false; absent otherwise.
traces_hs_codesobject[] | nullMatched-entry field: the codes TRACES accepts under the matched Annex entry, same shape as accepted_hs_codes. [] when that entry is out of scope or TRACES accepts no code under it; null when no entry matched (matched_by is broader or none).
finer_codes_acceptedboolean | nullMatched-entry field: whether TRACES also accepts finer codes under the entry's own code (e.g. any 6-digit 4403xx under 4403). null when no entry matched.
traces_fileableboolean | nullMatched-entry field: whether a DDS can be filed against the matched entry at all. null when no entry matched.
out_of_scope_kindenum | nullWhy the code is out of scope (see below). null when in scope.
out_of_scope_reasonstring | nullHuman-readable explanation of out_of_scope_kind. null when in scope.
dds_requiredbooleanfalse for every out-of-scope code, whatever the kind; true when in scope.
superseded_bystring[] | nullTRACES-fileable wire codes that replace this code under the Delegated Act (e.g. 1201 → ["120190"], 4012 → ["40129030"]). A code can list itself (120190 → ["120190"]): it stays in scope as a narrower entry. [] means the code is superseded with no TRACES-fileable replacement. null means the code is not, and does not fall under, a superseded code.
supplementary_unitobject | nullTRACES's supplementary-unit rule for traces_wire_hs_code: { rule, qualifier, required_for_activities }. rule is required (with qualifier MTQ, NAR or KSD, and required_for_activities ["IMPORT", "EXPORT"]) or not_allowed (qualifier null, required_for_activities []). optional is in the enum but never returned. On DOMESTIC no unit is needed. null when out of scope.

Coffee policy note: TRACES acceptance accepts bare 0901 (IMPORT and EXPORT re-probed on 2026-10-19), and Epoch now allows the bare heading as well as its five subheadings. Production has not been verified. Coffee acceptance fields and examples here describe the updated fm-api policy.

Code-level and matched-entry fields. traces_accepted, traces_wire_hs_code, traces_truncated and accepted_hs_codes judge the code you sent. traces_hs_codes, finer_codes_accepted and traces_fileable describe one Annex entry, the one in matched_cn_code. They can differ: 0901 matches the coffee entry, which is fileable (traces_fileable: true), and the bare heading is accepted (traces_accepted: true) without alternative-code suggestions.

out_of_scope_kind

Checked in this order; the first that holds wins.

KindWhenout_of_scope_reason
annex_exclusionThe matched entry is a product Annex I excludes (bamboo, recovered fibre, cotton linters, non-Bos bovines)From the entry, e.g. excluded from EUDR (bamboo or recovered fibre); no DDS required
removedThe matched entry was removed from Annex I by this versionremoved from EUDR Annex I by <version source>; no DDS required
supersededThe code is, or falls under, a code this version's Delegated Act replaced; superseded_by lists the replacementssuperseded by the 2026 Delegated Act; no DDS required unless the product falls under 120190, or …; no DDS required when superseded_by is []
not_yet_in_forceThe code is in scope in the next scope version, not this onein EUDR scope from 2027-12-30; no DDS required before then
not_in_annexNone of the abovenot listed in EUDR Annex I; no DDS required

meta — which version answered

FieldTypeDescription
scope_versionstringResolved scope-version key.
scope_version_statusenumdraft, adopted or superseded.
sourcestringCitation for the version's authority.
effective_from / effective_tostring | nullThe version's window.

Answers that are not a simple yes/no

  • traces_accepted: false — the code is in scope, but TRACES will refuse it, and a DDS declaring it is blocked with hs_code_not_accepted_by_traces. Declare one of accepted_hs_codes instead. When that list is empty, the product can't be filed through TRACES today.
  • recognised: false — the submitted code is not an Annex I entry itself. Recognition is separate from scope: an Annex entry that this version puts out of scope is still recognised, so recognised: true with in_scope: false is a valid answer. An Annex entry may still cover an unrecognised code: matched_cn_code then carries that entry's code. When matched_cn_code is null and matched_by is broader, the code is only a prefix of one or more entries — send a more specific code. Neither value says what TRACES receives: read traces_wire_hs_code.
  • ex_heading: true — the Annex entry covers only part of the heading. in_scope is true, but a specific product inside that heading may not be; confirm against annex_description before relying on it.
  • candidate_commodities non-empty — the code was broad enough to straddle more than one in-scope commodity, so commodity is null. Send a longer code to resolve it.
  • applicable_exemptions non-empty — an in-scope product can still be exempt (e.g. as single-use packing material, 100% recycled material, or waste). The endpoint lists what is available under this scope version; deciding whether one applies to your consignment is yours.

Status codes

CodeWhen
200Classified — including a clean in_scope: false
400hs_code malformed (not 2–8 digits), or an impossible as_of date. The detail rides in meta
401API key missing, invalid or revoked
403Key lacks both eudr:reference and eudr:read
404No scope version resolves for the requested version / as_of

List Annex I Entries enumerates every entry of the resolved version — use it to build a searchable picker so users can only choose valid, in-scope codes rather than classifying free text after the fact.

Required scope: eudr:reference or eudr:read