# EU CN Codes 2026: Combined Nomenclature lookup (`clearsource/eu-cn-codes`) Actor

Look up EU Combined Nomenclature 2026 goods codes with full hierarchy, EN/RO/DE descriptions and supplementary units, list children, or search candidate codes by keyword. Bundled Eurostat data, no network at run time. Not a customs classification decision.

- **URL**: https://apify.com/clearsource/eu-cn-codes.md
- **Developed by:** [PPFTEC S.R.L](https://apify.com/clearsource) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## EU CN codes 2026 (Combined Nomenclature lookup)

> Operator: PPFTEC S.R.L. (https://ppftec.com). Source allow-listed with conditions (Petre, 2026-10-10): lookup only, candidate codes, no duty rates.

Look up codes of the EU **Combined Nomenclature 2026 (CN 2026)**, the 8-digit goods nomenclature used in the EU for customs and trade statistics. Each record gives the code, its level, its full hierarchy (section → chapter → heading → subheading), descriptions in **English, Romanian and German**, the Eurostat self-explanatory text (EN/DE), the supplementary unit, a disclaimer and full provenance.

Three modes:

- **lookup**: exact records for up to 500 codes (`8471 30 00`, `84713000`, `8471.30`, `8471`, `84` or a section numeral such as `XVI`).
- **search**: keyword search over the official descriptions; returns ranked **candidate** codes.
- **children**: the direct children of one code (or the 21 sections when no code is given), to browse the tree.

The official Eurostat dataset is bundled in the actor image: **no network call at run time**, results in about a second.

**For:** e-commerce and logistics teams preparing customs data, EUDR/CBAM scoping, trade-data and ERP teams, and AI agents that need CN codes as JSON.

> **Candidate codes for information only — not a customs classification decision or binding tariff information. Only the Official Journal text is authentic. Not affiliated with the European Union.** For a legally binding classification ask your customs authority (Binding Tariff Information). No duty rates, quotas or TARIC measures are provided.

### Quick start

Look up two codes (with their full hierarchy):

```json input
{ "mode": "lookup", "codes": ["8471 30 00", "0901 21 00"] }
```

Find candidate codes for "roasted coffee" (English; `ro` and `de` also work):

```json input
{ "mode": "search", "query": "roasted coffee", "language": "en", "maxResults": 20 }
```

Browse: the direct children of heading 0802 (nuts):

```json input
{ "mode": "children", "codes": ["0802"] }
```

Validate an input without emitting or paying anything (the plan is stored under key `DRY_RUN`):

```json input
{ "mode": "lookup", "codes": ["8471", "2807", "9999 99 99"], "dryRun": true }
```

### Output example

One real record (lookup `0901 21 00`). Every record validates against `src/schemas/cn-code.schema.json` before it is pushed; a record that fails is a bug and fails the run.

```json output
{
  "record_type": "cn-code",
  "result_kind": "exact",
  "query": "0901 21 00",
  "cn_version": "CN 2026",
  "node_id": "090121000080",
  "cn_code": "0901 21 00",
  "cn_code_8": "09012100",
  "level": "cn_subheading",
  "depth": 5,
  "parent_code": "0901",
  "parent_node_id": "090121000010",
  "description": {
    "en": "Not decaffeinated",
    "ro": "Nedecafeinizată",
    "de": "nicht entkoffeiniert"
  },
  "self_explanatory_text": {
    "en": "Roasted coffee (excl. decaffeinated)",
    "de": "Kaffee, geröstet, unentkoffeiniert"
  },
  "supplementary_unit": null,
  "is_leaf": true,
  "path": [
    {
      "node_id": "060011000090",
      "cn_code": "II",
      "level": "section",
      "description_en": "SECTION II - VEGETABLE PRODUCTS"
    },
    {
      "node_id": "090021000090",
      "cn_code": "09",
      "level": "chapter",
      "description_en": "CHAPTER 9 - COFFEE, TEA, MATÉ AND SPICES"
    },
    {
      "node_id": "090100000080",
      "cn_code": "0901",
      "level": "heading",
      "description_en": "Coffee, whether or not roasted or decaffeinated; coffee husks and skins; coffee substitutes containing coffee in any proportion"
    },
    {
      "node_id": "090121000010",
      "cn_code": null,
      "level": "grouping",
      "description_en": "Coffee, roasted"
    }
  ],
  "match_score": null,
  "note": null,
  "concept_uri": "http://data.europa.eu/xsp/cn2026/090121000080",
  "source_data": {
    "dataset_url": "https://data.europa.eu/data/datasets/combined-nomenclature-2026",
    "dataset_modified": "2026-02-18",
    "data_sha256": "8b52199ff82d1a1dbc6720fd6b5e4e6327f33b412e4c91cc31ee81f7ceea07d9",
    "raw_sha256": "0a66e97dda47f93c5098e99511aa74c1923ae499656858bd6a86ecccf83efd34",
    "built_at": "2026-10-10T11:15:07.314Z"
  },
  "disclaimer": "Candidate codes for information only — not a customs classification decision or binding tariff information. Only the Official Journal text is authentic. Not affiliated with the European Union.",
  "provenance": {
    "envelope_version": "1.0.0",
    "source_id": "eu-combined-nomenclature-2026",
    "source_url": "https://op.europa.eu/o/opportal-service/euvoc-download-handler?cellarURI=http%3A%2F%2Fpublications.europa.eu%2Fresource%2Fcellar%2F79c210b8-c4a3-11f0-8da2-01aa75ed71a1.0001.02%2FDOC_1&fileName=ESTAT-CN2026.rdf",
    "retrieved_at": "2026-10-10T11:11:50.000Z",
    "source_version": {
      "dataset_revision": "combined-nomenclature-2026@2026-02-18",
      "record_version": "CN 2026",
      "etag": "\"Con-20260701165953000\"",
      "last_modified": "2026-07-01T14:59:53.000Z"
    },
    "producer": {
      "name": "eu-cn-codes",
      "version": "0.1.0",
      "build": null,
      "run_id": null,
      "adapter": "cn2026-rdf-bundle"
    },
    "record_hash": "sha256:9ba3bc17037fb9d487459c7b370fc470b6aeb0290ac72e176865e25426ccedc9",
    "licence": {
      "id": "EC-Decision-2011-833",
      "url": "https://eur-lex.europa.eu/eli/dec/2011/833/oj",
      "attribution": "Source: Eurostat / Publications Office of the EU, Combined Nomenclature 2026 (data.europa.eu), reused under Commission Decision 2011/833/EU; modified (flattened, normalised). Only the Official Journal text is authentic."
    },
    "personal_data": "none",
    "source_request": {
      "method": "GET"
    },
    "source_record_url": "http://data.europa.eu/xsp/cn2026/090121000080"
  }
}
```

### Fields

| Field | Notes |
| --- | --- |
| `result_kind` | `exact` (lookup), `candidate` (search; never a classification), `child` (children), `not_found` (lookup code not in CN 2026; free). |
| `query` | The code or keywords from your input that produced the record. |
| `cn_code` | Code as printed in the CN: section numeral, `84`, `8471`, `8471 30`, `8471 30 00`; `null` for code-less grouping lines ("- Coffee, roasted"). |
| `cn_code_8` | 8 digits without spaces, CN subheadings only. |
| `level`, `depth` | `section`, `chapter`, `heading`, `hs_subheading`, `cn_subheading` or `grouping`; depth in the tree (section = 1). |
| `parent_code`, `parent_node_id` | Nearest ancestor with a code; direct parent node (may be a grouping line). |
| `node_id` | 12-digit id of the node in the dataset; also accepted as input (to browse grouping lines). |
| `description` | `{en, ro, de}` official descriptions without the code and dash markers. Read them with `path`: "Other" only makes sense under its parents. |
| `self_explanatory_text` | Eurostat stand-alone description (`en`, `de`) when published; `null` otherwise. |
| `supplementary_unit` | Code as published, e.g. `PST` (number of items), `M2`, `M3`, `L`, `PA` (pairs); `null` when none. |
| `is_leaf` | `true` when the node has no children. |
| `path` | Ancestors from the section down to the direct parent: `node_id`, `cn_code`, `level`, `description_en`. |
| `match_score` | Search only: relative keyword-match score 0–1 (how well the words match), not a probability that the code is right. |
| `note` | E.g. "`2807` is not subdivided separately in CN 2026; returned 2807 00 00", the TARIC 10-digit note, or the candidate note. |
| `concept_uri`, `source_data` | Dataset URI of the node; dataset URL, modified date, data and raw-file sha256, data build time. |
| `disclaimer`, `provenance` | On every record. Provenance: source, retrieval time of the official file, dataset revision, record hash, licence. |

Input codes: spaces, dots and hyphens are ignored. A heading or HS subheading that CN 2026 does not subdivide exists only as its 8-digit code (e.g. `2807` → `2807 00 00`); the actor returns that code and says so in `note`. 10-digit TARIC codes are looked up by their first 8 digits (with a note).

### Pricing (pay per event)

| Event | Price | When |
| --- | --- | --- |
| `apify-actor-start` | $0.00005 (Apify default) | per run |
| `code-result` | $0.0005 | per record pushed with a code (`not_found` rows are free) |

Nothing is charged for dry runs or invalid input. Set a maximum charge per run in Apify; the actor stops cleanly when it is reached.

### Limits

- Lookup: at most 500 codes per run. Search and children: `maxResults` 1–1,000 (default 50). Keywords: at most 200 characters.
- **Search is plain keyword matching**, not AI classification: it matches the words of the official texts (EN, RO, DE descriptions; self-explanatory texts in EN/DE; parent descriptions). Trade names often fail ("laptop" finds nothing; "portable data-processing machines" finds 8471 30 00). All words must match; if nothing matches all words, partial matches are returned with lower scores.
- Data: CN 2026 only. No duty rates, quotas, suspensions, TARIC measures, legal notes or explanatory notes. CN 2027 applies from 1 January 2027; the actor will be rebuilt from the new official dataset.
- The data is a snapshot of the official file (see `provenance.retrieved_at` and `source_data`); corrections published later are included only after a rebuild.

### Data source and licence

Source: **Combined Nomenclature, 2026 (CN 2026)**, dataset `combined-nomenclature-2026` on data.europa.eu (https://data.europa.eu/data/datasets/combined-nomenclature-2026), creator Eurostat, publisher Publications Office of the EU; SKOS_CORE RDF/XML distribution `ESTAT-CN2026.rdf` (dataset modified 2026-02-18; file downloaded once on 2026-10-10, sha256 `0a66e97d…fd34`). The CN 2026 concept scheme is based on Commission Implementing Regulation (EU) 2025/1926 (http://data.europa.eu/eli/reg_impl/2025/1926/oj).

Attribution (also in every record's `provenance.licence` and in the dataset description): **Source: Eurostat / Publications Office of the EU, Combined Nomenclature 2026 (data.europa.eu), reused under Commission Decision 2011/833/EU; modified (flattened, normalised). Only the Official Journal text is authentic.**

Reuse: commercial and non-commercial reuse is allowed under Commission Decision 2011/833/EU (https://eur-lex.europa.eu/eli/dec/2011/833/oj), with acknowledgement of the source. **Modifications:** this actor keeps 11 fields per node, removes the code and dash markers from descriptions, keeps EN/RO/DE only, and computes hierarchy paths, levels, the 8-digit form and search scores. This actor is **not affiliated with or endorsed by** the European Union, Eurostat or the Publications Office. No EU logo is used.

### Reliability

- **Data integrity.** At start the actor checks the sha256 of the bundled data against `data/manifest.json`; a mismatch stops the run (exit 1). Every record is validated against the output schema before it is pushed.
- **Build fails loudly on drift.** `scripts/build-data.ts` stops on an unknown notation pattern, a node without an English label, a missing parent, a cycle or unexpected counts (21 sections, 97 chapters, 9,791 CN subheadings in CN 2026).
- **Kill switch.** The maintainer can disable all runs with `PDA_KILL_SWITCH=on` (status `Disabled by maintainer: …`, nothing emitted or charged).
- **No network.** The actor makes no HTTP request at run time.
- **Changelog:** see `CHANGELOG.md`.

### Use with AI assistants (MCP)

This actor can be called by AI assistants through Apify's MCP server (a service run by Apify; this actor is a separate, independent product of PPFTEC S.R.L., not affiliated with the EU or Eurostat). Add it to an MCP client such as Claude, VS Code or Cursor:

```
{
  "mcpServers": {
    "clearsource-eu-cn-codes": {
      "url": "https://mcp.apify.com?tools=clearsource/eu-cn-codes"
    }
  }
}
```

On first use your browser asks you to sign in to Apify (OAuth); or send your token as `Authorization: Bearer <APIFY_TOKEN>`. The assistant reads the input schema, runs the actor and then fetches the dataset items. Runs are billed to your Apify account at the pay-per-event prices above. Results remain candidate codes: the assistant should show the disclaimer and the official descriptions.

Try: "Which CN 2026 codes are candidates for roasted coffee, and what is the hierarchy of 0901 21 00?" Details of the MCP server: https://docs.apify.com/platform/integrations/mcp (checked 2026-10-10).

### Terms of use

This actor is offered by PPFTEC S.R.L. under Apify's terms plus our terms of use: https://clearsource.ppftec.com/terms

### Disclaimer

Candidate codes for information only — not a customs classification decision or binding tariff information. Only the Official Journal text is authentic. Not affiliated with the European Union. Data is provided "as is", as published in the official dataset at `provenance.retrieved_at`. This is not legal or customs advice.

### Calling via API

```bash
curl -X POST "https://api.apify.com/v2/acts/clearsource~eu-cn-codes/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"mode":"lookup","codes":["8471 30 00"]}'
```

### Development

```bash
npm ci
npm run lint && npm run typecheck && npm test && npm run build
## Rebuild the bundled data (once per CN release or correction):
node scripts/build-data.ts --download   # ONE download of metadata + RDF (171 MB) into data-raw/ (gitignored)
node scripts/build-data.ts              # parse data-raw/ESTAT-CN2026.rdf -> data/cn2026.json + data/manifest.json
npm run gen:dataset-schema
```

User-Agent of the download: `clearsource/eu-cn-codes@<version> (+https://clearsource.ppftec.com)`. The raw RDF is never committed; `data/cn2026.json` (about 6.8 MB) and `data/manifest.json` are.

# Changelog

This Actor's version history is a separate document: https://apify.com/clearsource/eu-cn-codes/changelog.md

# Actor input Schema

## `mode` (type: `string`):

lookup: exact records (with full hierarchy) for the codes you give. search: candidate codes for keywords (not a classification). children: direct children of one code (or all sections when empty).

## `codes` (type: `array`):

lookup: up to 500 codes, e.g. 8471 30 00, 84713000, 8471.30, 8471, 84 or a section numeral (XVI). children: one code (empty = list the 21 sections).

## `query` (type: `string`):

search mode: words describing the goods, e.g. 'roasted coffee'. Matched against the official descriptions and Eurostat self-explanatory texts. Results are candidate codes only.

## `language` (type: `string`):

Language of your keywords (search mode). Every record always carries EN, RO and DE descriptions.

## `maxResults` (type: `integer`):

search and children modes: maximum records returned.

## `dryRun` (type: `boolean`):

Validate the input and report how many records would be returned; nothing is emitted or charged.

## Actor input object example

```json
{
  "mode": "lookup",
  "codes": [
    "8471 30 00",
    "0901 21 00"
  ],
  "language": "en",
  "maxResults": 50,
  "dryRun": false
}
```

# Actor output Schema

## `results` (type: `string`):

One record per code (lookup), candidate code (search) or child (children), with hierarchy, disclaimer and provenance.

## `runSummary` (type: `string`):

Key-value store records such as DRY_RUN when present.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "codes": [
        "8471 30 00",
        "0901 21 00"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("clearsource/eu-cn-codes").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "codes": [
        "8471 30 00",
        "0901 21 00",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("clearsource/eu-cn-codes").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "codes": [
    "8471 30 00",
    "0901 21 00"
  ]
}' |
apify call clearsource/eu-cn-codes --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,clearsource/eu-cn-codes"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Wo7yljlR6dDSWPqGb/builds/WEh4UsXg7rUqPhtOh/openapi.json
