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
| Parameter | Required | Description |
|---|---|---|
hs_code | yes | The code to classify. 2–8 digits; separators (spaces, dots) are stripped before matching, so 4403, 44.03 and 44 03 are equivalent. |
as_of | no | Resolve the scope version in force on this date (ISO date). Defaults to today. |
version | no | Pin an explicit scope-version key, including drafts — for previewing a pending Delegated Act before it is adopted. |
▶Response
▶data
data| Field | Type | Description |
|---|---|---|
hs_code | string | The submitted code, normalised to bare digits. |
in_scope | boolean | Whether the code falls within EUDR Annex I scope. |
commodity | string | null | Canonical EUDR commodity (cattle, cocoa, coffee, palm, rubber, soy, timber), or null when out of scope or ambiguous. |
matched_cn_code | string | null | The Annex I CN code that matched, or null. |
matched_by | enum | Granularity of the match — chapter, heading, subheading, tariff, broader, or none. |
recognised | boolean | true 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_heading | boolean | true when the matched entry carries an "ex" prefix: only part of the heading is in scope, so confirm the specific product. |
annex_description | string | null | Annex I product description for the matched entry. |
candidate_commodities | string[] | Populated when a broad code overlaps more than one in-scope commodity — supply a more specific code to disambiguate. |
applicable_exemptions | object[] | 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. |
notes | string[] | Human-readable caveats about the classification. |
traces_accepted | boolean | null | Whether TRACES accepts the code, after any truncation to traces_wire_hs_code. null when out of scope. |
traces_wire_hs_code | string | null | The code fm-api would send TRACES (e.g. 010229 → 0102). null when out of scope. |
traces_truncated | boolean | null | true 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_codes | object[] | 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_codes | object[] | null | Matched-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_accepted | boolean | null | Matched-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_fileable | boolean | null | Matched-entry field: whether a DDS can be filed against the matched entry at all. null when no entry matched. |
out_of_scope_kind | enum | null | Why the code is out of scope (see below). null when in scope. |
out_of_scope_reason | string | null | Human-readable explanation of out_of_scope_kind. null when in scope. |
dds_required | boolean | false for every out-of-scope code, whatever the kind; true when in scope. |
superseded_by | string[] | null | TRACES-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_unit | object | null | TRACES'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
out_of_scope_kindChecked in this order; the first that holds wins.
| Kind | When | out_of_scope_reason |
|---|---|---|
annex_exclusion | The 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 |
removed | The matched entry was removed from Annex I by this version | removed from EUDR Annex I by <version source>; no DDS required |
superseded | The code is, or falls under, a code this version's Delegated Act replaced; superseded_by lists the replacements | superseded 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_force | The code is in scope in the next scope version, not this one | in EUDR scope from 2027-12-30; no DDS required before then |
not_in_annex | None of the above | not listed in EUDR Annex I; no DDS required |
▶meta — which version answered
meta — which version answered| Field | Type | Description |
|---|---|---|
scope_version | string | Resolved scope-version key. |
scope_version_status | enum | draft, adopted or superseded. |
source | string | Citation for the version's authority. |
effective_from / effective_to | string | null | The 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 withhs_code_not_accepted_by_traces. Declare one ofaccepted_hs_codesinstead. 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, sorecognised: truewithin_scope: falseis a valid answer. An Annex entry may still cover an unrecognised code:matched_cn_codethen carries that entry's code. Whenmatched_cn_codeisnullandmatched_byisbroader, the code is only a prefix of one or more entries — send a more specific code. Neither value says what TRACES receives: readtraces_wire_hs_code.ex_heading: true— the Annex entry covers only part of the heading.in_scopeistrue, but a specific product inside that heading may not be; confirm againstannex_descriptionbefore relying on it.candidate_commoditiesnon-empty — the code was broad enough to straddle more than one in-scope commodity, socommodityisnull. Send a longer code to resolve it.applicable_exemptionsnon-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
| Code | When |
|---|---|
200 | Classified — including a clean in_scope: false |
400 | hs_code malformed (not 2–8 digits), or an impossible as_of date. The detail rides in meta |
401 | API key missing, invalid or revoked |
403 | Key lacks both eudr:reference and eudr:read |
404 | No scope version resolves for the requested version / as_of |
▶Related
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:referenceoreudr:read