# What CLP classification applies on the date you ask? > Annex VI moves by annual ATP, past every model cutoff. One call returns the CLP label in force at the date you ask, plus the change already published. Published 2026-08-10 · HTML version: https://invoket.com/blog/what-clp-classification-applies-on-the-date-you-ask --- "What is the harmonised classification of this substance?" is a question with no answer. **Annex VI to the CLP Regulation** is versioned: it is amended by an **Adaptation to Technical Progress (ATP)** that is published years before it becomes mandatory, so the same CAS number carries different hazard classes, different H-statements and a different pictogram set depending on **which date you are labelling for**. [`GET /label/chem/classify`](/docs/api/label-chem-classify) answers the dated version of the question: the Annex VI entry **in force at the `at` you pass**, the act it comes from with its CELEX number, and an `upcoming` block when a published ATP will change it — served offline, with no network call and no secret at request time. ## The regulation publishes the future, and models never see it Two adaptations are live objects right now, and they bracket today: - **ATP 22** — Commission Delegated Regulation (EU) **2024/2564**, adopted 19 June 2024, published in the OJ on 30 September 2024, in force since 20 October 2024, **mandatory from 1 May 2026**. It introduced 27 new harmonised classifications, modified 16 existing entries and deleted 7. - **ATP 23** — Commission Delegated Regulation (EU) **2025/1222**, adopted 2 April 2025, published in the OJ on 20 June 2025, in force since 10 July 2025, **mandatory from 1 February 2027**. Read the gap. ATP 23 has been law since July 2025 and will not bite until February 2027 — a nineteen-month window in which suppliers **may** apply the new classification voluntarily and are **not yet obliged** to. During that window two different labels for the same substance are both lawful, and only the compliance date decides which one your agent should print. That is why an undated answer is not merely imprecise, it is unusable. A language model answering from recall returns whichever Annex VI it last saw — usually one adaptation behind, with no signal that a published successor exists. The failure is silent: fluent hazard classes, correct-looking H-statements, and no date attached to any of it. ## Same CAS, two dates, two labels Nitrous oxide (CAS **10024-97-2**) is the clean case. Called today, with no `at`, the service resolves against the snapshot in force: ```json { "data": { "verdict": "not_harmonised", "entries": [], "note": "Not listed in the harmonised classification (Annex VI CLP) in force at this date. Absence does not mean the substance is not hazardous: self-classification under CLP remains the responsibility of the entity placing it on the market.", "upcoming": { "change": "added", "regulation": "Annex VI to CLP — ATP23 (applies from 2027-02-01)", "applicable_from": "2027-02-01" } }, "provenance": { "source": "ECHA Annex VI (CLP), harmonised classification and labelling", "freshness": { "kind": "snapshot", "as_of": "2026-05-01T00:00:00Z" } } } ``` Not harmonised — **and** a published adaptation that will add it. The same call with `at=2027-06-01` returns the entry that will then be in force: ```json { "index_number": "007-031-00-9", "name": "dinitrogen oxide", "ec_numbers": ["233-032-0"], "cas_numbers": ["10024-97-2"], "hazard_classes": [ { "class": "Repr.", "category": "1B", "h_statement": "H360Df" }, { "class": "STOT SE", "category": "3", "h_statement": "H336" }, { "class": "STOT RE", "category": "1", "h_statement": "H372 (nervous system)" }, { "class": "Ozone", "category": "1", "h_statement": "H420" } ], "pictograms": ["GHS08", "GHS07"], "signal_word": "Dgr", "cmr_1a_1b": true, "atp": { "regulation": "Annex VI to CLP — entry version ATP23 (CELEX 32025R1222)", "applicable_from": "2027-02-01", "celex": "32025R1222", "eurlex_url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32025R1222" } } ``` Nothing about the substance changed between the two calls. What changed is the date the question was asked for: no harmonised entry at all on one side, a **Repr. 1B** reproductive toxicant with `cmr_1a_1b: true` on the other. An agent that asks the undated question gets one of these two answers by accident. ## `upcoming` is the field that makes a stale answer visible Every response carries the change that is already published but not yet applicable — `added`, `modified` or `removed`, with the ATP that will apply and its date. On a `not_harmonised` verdict it sits at the root of `data`; on a harmonised entry it sits on the entry itself. This is the part no model can hold, because it is about a version of the law that does not govern yet. It is also the part that decides real work: a formulation being re-labelled for a 2027 shipment, an SDS being drafted now for a product with an eighteen-month lead time, a supplier questionnaire that must flag substances about to become CMR. One deliberate exclusion keeps the flag honest: the act reference (`celex` / `eurlex_url`) is **excluded** from the in-force ↔ upcoming comparison. An entry merely re-attributed to a different act never raises a false `upcoming: modified`. ## Two dates in `atp`, and they are not the same date The `atp` block is where the versioning becomes readable, and it carries a distinction worth wiring into an agent. Ethylbenzene, served on `2026-06-01`: ```json "atp": { "regulation": "Annex VI to CLP — entry version ATP06 (CELEX 32014R0605)", "applicable_from": "2026-05-01", "celex": "32014R0605", "eurlex_url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32014R0605" } ``` - **`applicable_from`** is the date of the **snapshot served** — the latest adaptation in application at your `at`. - **`celex`** is the act that set **this entry's version**, which is usually much older. Ethylbenzene was last touched by the 2014 adaptation ATP 06; no adaptation since has changed it. So two entries returned from the *same* snapshot routinely come from different acts: n-hexane is served in its `ATP22` version (CELEX `32024R2564`), while most of Annex VI is still served in its base-regulation version `CLP00` (CELEX `32008R1272`). `celex` and `eurlex_url` are copied from the source row, never derived — an entry whose version the source leaves without a CELEX carries neither field. They are what lets an agent walk from a served hazard class back to the legal text that founds it. ## `not_harmonised` is a paid answer, and it says so out loud A well-formed CAS that Annex VI does not carry returns a **200**, billed, with an empty `entries` and the self-classification note quoted above. This is the x402 golden rule applied to a register: the agent asked "is this substance harmonised under CLP?", and "no, at this date" is the answer it paid for — never a 404. The note matters as much as the verdict. **Absence from Annex VI does not mean the substance is not hazardous**: harmonised classification covers a minority of substances on the market, and self-classification under CLP remains the duty of whoever places the substance there. A verdict field alone would invite exactly the wrong inference, so the note ships with every one of them. Only requests the service *cannot* answer leave the 200 range, and none of them are billed: ```json { "code": "INVALID_INPUT", "error": "parameters 'cas', 'ec' and 'name' are mutually exclusive" } ``` `cas` and `ec` are **check-digit validated before any lookup**, with the same rigour as an IBAN's mod-97 — a transposed digit is rejected by name rather than resolved confidently into the wrong substance. ## What the date window actually covers Three boundaries are worth knowing before an agent starts passing `at` freely. - **Backwards, the store has a floor.** A date before the earliest adaptation held returns a `400` that says so: `'at' predates the earliest Annex VI adaptation in store (2026-05-01)`. The endpoint serves the current and the coming Annex VI, **not a historical archive** of every adaptation since 2008. - **Forwards, it stops at what is published.** A date beyond the last published ATP is served from that last ATP — `at=2030-01-01` today answers from the 2027-02-01 snapshot, and `freshness.as_of` says so. That is the honest behaviour: adaptations that do not exist yet cannot be predicted. - **Names are exact, never fuzzy.** `name=nitrous oxide` returns `not_harmonised` at every date; the Annex VI name is **dinitrogen oxide**. Common names, trade names and synonyms are not resolved — an agent working from free text should resolve to a CAS or EC number first. An ambiguous exact name is never disambiguated silently either: all of its entries come back. Two more lines that are the service's boundary rather than its limitations. The class ↔ H-statement pairing is **positional and cautious** — `h_statement` is filled only when the source columns align, and is served `null` rather than paired at random, with the flat `hazard_statements` list still carrying everything. And `cmr_1a_1b` is a **derived screening shortcut**, true when a Carc./Muta./Repr. 1A or 1B class is among those published; it is a convenience over the published classes, not a judgment of ours. What the endpoint never does: classify a **mixture** from its components, generate a safety data sheet, or give legal advice. It relays Annex VI as dated, sourced, versioned facts. That line is the product. ## Where it sits in the x402 loop No account, no key, no contract review precedes the first call: 1. **Discover** the endpoint in the live catalog, call it, receive the `402` challenge. 2. **Pay** — sign the chosen rail and replay the request. 3. **Read** — `verdict`, the entry's classes, pictograms and signal word, `atp.celex`, `upcoming`, and the `as_of` that dates all of it. 4. **Branch** — print the label, schedule a re-label for the `upcoming` date, or route the substance to a human with the EUR-Lex link cited. A bill of materials does not need one settlement per line: [`POST /label/chem/classify/batch`](/docs/api/label-chem-classify-batch) screens up to **200** substances — mixing CAS, EC and exact names — in a single call, **all evaluated at the same `at`**, priced per unit. Running a whole formulation at `at=2027-02-01` is how you find out today which lines change under ATP 23. The [Quickstart](/docs/quickstart) walks the full discover → `402` → pay → replay cycle with runnable snippets. Price and accepted rails are served live by the [catalog](https://api.invoket.com/catalog), never pinned in an article. ## Freshness, and why staleness is never a 5xx Answers come from a **snapshot** of the official ECHA Annex VI dataset, dated by the ATP in force at your `at` rather than by the ingestion date — the regulatory version *is* the answer. Refreshes happen off-band, once or twice a year, on the rhythm of the adaptations themselves. A missed refresh keeps serving the last good snapshot with `freshness.as_of` telling the truth about it; a dataset never ingested at all is a `503 DATA_UNAVAILABLE`, because there is no last good snapshot to serve. ## Chain it with the rest of the label family A dated CLP verdict composes with the other dated verdicts: - [`GET /label/chem/svhc`](/docs/api/label-chem-svhc) — whether the substance is on the REACH Candidate List, with its Article 57 grounds and the supply-chain duties inclusion triggers. - [`GET /label/cosmetic/ingredient`](/docs/api/label-cosmetic-ingredient) — prohibited, restricted or annex-authorised status under Regulation (EC) No 1223/2009, versioned the same way. - [`GET /label/food/additive`](/docs/api/label-food-additive) — whether an additive is authorised in a given food category, at which level. `chem/classify` and `chem/svhc` are the usual pre-market pair: the first says what the label must carry, the second says what the supply chain must be told. Both answer from a dated publication rather than from recall — the only property that makes either safe to put behind an autonomous agent. For the full field reference and error codes, see the [`GET /label/chem/classify` documentation](/docs/api/label-chem-classify); for how agents discover and call Invoket endpoints, see [For agents](/docs/for-agents).