Is this cosmetic ingredient allowed in the EU?
Prohibited, restricted or annex-authorised under Regulation 1223/2009 — one call returns every Annex entry that covers a substance, with the label wording served word for word.
By Matthias Begot ·
“Can we use this ingredient?” is not a yes/no question in EU cosmetics law. A
substance can be prohibited under Annex II and simultaneously restricted
for one narrow professional use under Annex III; it can be authorised as a
preservative at one concentration and restricted as a plain ingredient at
another. The obligations stack, and the entry that governs depends on the
consolidated version of the Regulation in force on the date you ask.
GET /label/cosmetic/ingredient returns
every Annex entry covering the substance, each with its published maximum
concentration, conditions of use and mandatory label wording served word for
word — resolved from a local store, with no network call and no secret at
request time.
The version in force right now was written in January 2026
Regulation (EC) No 1223/2009 is not a fixed text. It is amended several times a year, and the amendment that governs today is recent enough to sit past most model cutoffs:
- Commission Regulation (EU) 2026/78 of 12 January 2026, on the use in cosmetic products of certain substances classified as carcinogenic, mutagenic or toxic for reproduction — published in the OJ on 13 January 2026, and applicable from 1 May 2026. It amends Annexes II, III, IV and V, adding Annex II entries 1752 to 1766 and Annex III entries 379 and 380.
That is the version the endpoint serves: version.consolidated_ref reads
02009R1223 — EN — 01.05.2026. And it changes real answers. Acetone oxime
(CAS 127-06-0) was an ordinary ingredient until 30 April 2026 and is a
prohibited substance from 1 May:
{
"verdict": "prohibited",
"substance": {
"chemical_name": "Acetone oxime",
"cas_numbers": ["127-06-0"],
"ec_numbers": ["204-820-1"],
"resolved_from": "annexes"
},
"entries": [
{
"annex": "II",
"entry_number": "1754",
"status": "prohibited",
"conditions": [],
"amending_act": "COMMISSION REGULATION (EU) 2026/78 of 12 January 2026",
"amending_celex": "32026R0078",
"amending_oj_date": "2026-01-13",
"applicable_from": "2026-05-01"
}
]
}
Silver is the other shape of the same amendment — not banned outright but pinned to two product types at a single level, under a brand-new Annex III entry:
{
"annex": "III",
"entry_number": "379",
"status": "restricted",
"conditions": [
{
"product_type_or_body_part": "(a) Toothpaste\n(b) Mouthwash",
"max_concentration": "For (a) and (b): 0,05 %"
}
],
"amending_act": "COMMISSION REGULATION (EU) 2026/78 of 12 January 2026",
"amending_celex": "32026R0078"
}
amending_celex is what makes the answer checkable: an agent can walk from a
served restriction straight back to the act on EUR-Lex that founds it.
Only the Annexes are authoritative — CosIng is informative
CosIng, the European Commission’s cosmetic-ingredient database, is a
convenient index and a legally non-binding one. Only the Annexes to the
Regulation carry legal force. The service keeps the two apart and tells you
which one spoke, in substance.resolved_from:
"annexes"— the identity came from the authoritative source itself;"cosing_index"— CosIng recognised the substance, which is what turns an unknown identifier into an honest datednot_listedinstead of a 404;source_divergence: truewith adivergences[]array — a recorded gap between CosIng and an Annex applies here. The Annex prevails, and the gap is reported rather than silently resolved.
Tools that stop at CosIng are answering from the informative layer and calling it compliance. The distinction is a field, not a footnote, precisely so an agent can branch on it.
One substance, several Annexes, all of them binding
Salicylic acid is the case that breaks single-verdict thinking. It is restricted under Annex III entry 98 and authorised as a preservative under Annex V entry 3, and both apply:
{
"verdict": "restricted",
"substance": {
"inci_name": "Salicylic acid",
"cas_numbers": ["69-72-7"],
"ec_numbers": ["200-712-3"],
"resolved_from": "annexes"
},
"entries": [
{
"annex": "III",
"entry_number": "98",
"status": "restricted",
"conditions": [
{
"product_type_or_body_part": "(a) Rinse-off hair products\n(b) Other products except body lotion, eye shadow, mascara, eyeliner, lipstick, roll-on deodorant\n(c) Body lotion, eye shadow, mascara, eyeliner, lipstick, roll-on deodorant",
"max_concentration": "(a) 3,0 %\n(b) 2,0 %\n(c) 0,5 %",
"other_conditions": "(a) (b) (c)\nNot to be used in preparations for children under 3 years of age. …\nThese levels are inclusive of any use of salicylic acid.",
"required_label_wording": "(a) (b) (c)\nNot to be used for children under 3 years of age (10)"
}
],
"amending_act": "COMMISSION REGULATION (EU) 2021/850 of 26 May 2021"
},
{ "annex": "V", "entry_number": "3", "status": "authorised_preservative", "…": "…" }
],
"note": "This substance is covered by 2 entries (III/98, V/3). The top-level verdict reflects the most restrictive one; each entry carries its own status and conditions, which all apply cumulatively."
}
The top-level verdict is the most restrictive status found. It is a
routing signal, not the whole answer — an agent that reads verdict and stops
loses half the obligations. note says so in words, on every multi-entry
substance.
Titanium dioxide behaves the same way (Annex III entry 321 and Annex VI entry 27 as a UV filter), with the Annex VI conditions explicitly cross-referencing the Annex III entry. Hydroquinone is the sharpest version: prohibited under Annex II entry 1339, with a single surviving Annex III exception for artificial nail systems, professional use only.
The label wording is the deliverable, and it is served verbatim
For a restricted or authorised entry, required_label_wording is what has to be
printed. It comes back exactly as published — dashes, line breaks and all:
"required_label_wording": "— For professional use only\n— Avoid skin contact\n— Read directions for use carefully"
Nothing is converted, rounded, reformulated or translated. "3,0 %" keeps its
decimal comma; "0,02 % (after mixing for use)" keeps its parenthetical, because
the parenthetical is the condition; a multi-part restriction keeps its (a)
(b) (c) structure, because that structure is what binds each concentration to
its product type. Reformatting regulatory text is how meaning gets lost, so the
service refuses to do it.
Key on the identifier when you have one
Four keys are accepted — cas, ec, inci, name — exactly one per call, and
cas and ec are check-digit validated before any lookup, the same
discipline an IBAN gets from mod-97. There is no fuzzy matching on names: an
approximate INCI name is a miss, not a guess.
That last rule has a practical consequence worth wiring in. Annex entries are frequently grouped: Annex V entry 12 covers “4-Hydroxybenzoic acid and its Methyl- and Ethyl-esters, and their salts” and publishes nine INCI names in a single field. And new CMR entries arrive under their chemical name, with no INCI column at all. So the number is the join that always holds:
cas=99-76-3→authorised_preservative, Annex V entry 12, resolved from the Annexes;cas=127-06-0→prohibited, Annex II entry 1754.
When your input is free text from a product page, resolve it to a CAS or EC
number first and key on that. inci and name are exact-match lookups into name
spaces that the Annexes and CosIng populate differently.
not_listed is a billed 200, and it is not an authorisation
A resolved identity that the Annexes do not carry is a successful answer —
200, billed, with an empty entries and a dated statement of what the absence
does and does not mean:
{
"verdict": "not_listed",
"substance": { "inci_name": "GLYCERIN", "cas_numbers": ["56-81-5"], "resolved_from": "cosing_index" },
"entries": [],
"note": "Not listed in Annexes II to VI of Regulation (EC) No 1223/2009 as in force on 2026-08-10 (consolidated version 02009R1223 — EN — 01.05.2026 of the Regulation). This verdict is dated and is not an authorisation: it means no specific prohibition or restriction entry applies to this substance at that date."
}
This is the x402 golden rule applied to a register: the agent asked whether the
Annexes carry the substance, and “no, at this date” is the answer it paid for —
never a 404. The unresolved case is the one that is a 404: an identity unknown
to both the Annexes and the CosIng index. There is nothing to answer about,
and nothing is billed.
The distinction between the two is the whole point. Absence from Annexes II to
VI is not an authorisation. It means no EU-wide prohibition or restriction entry
applies; the safety assessment of the ingredient and of the finished product stays
with the responsible person placing it on the market. That sentence ships in the
limits array of every single response, alongside three others: this is not a
safety assessment, values are served exactly as published, and CosIng is
informative only.
product_type filters, and counts what it removed
Passing product_type keeps the conditions whose published scope covers that
product type, plus the unbounded ones. It is a reading aid, and it is built so
it can never let an agent believe it saw everything.
Salicylic acid with product_type=Toothpaste is the instructive case. The Annex
III entry comes back with zero conditions and
"conditions_omitted_by_product_type": 1 — its conditions are all scoped to
product types that are not toothpaste. The Annex V preservative entry keeps its
conditions, because they carry no product-type scope of their own. The entry does
not disappear, and the count of what was trimmed travels with it.
What the date window covers
date serves the consolidated version in force at that date. Two boundaries
matter:
- Backwards, there is a floor. A date before the oldest ingested consolidated
version is a
400that names the bound:'date' predates the earliest consolidated version of Regulation (EC) No 1223/2009 in store (2026-05-01). The endpoint serves the version in force and the ones that follow — it is not a historical archive of every amendment since 2009. Extrapolating a verdict backwards would be inventing law. - Forwards, it stops at what is published. When an amendment is published and
ingested but not yet applicable, the affected entry carries an
upcomingblock —change,applicable_from,consolidated_ref— so a formulation being planned for a future launch can be checked against the version that will govern it. Amendments that do not exist yet are not predicted.
Screening a whole INCI list in one settlement
A formulation is not one ingredient.
POST /label/cosmetic/ingredient/batch
takes up to 200 items — mixing cas, ec, inci and name — evaluated at
one date, settled as a single x402 payment and priced per unit. Each result
stays at its position, including per-item failures (INVALID_INPUT for a bad
check digit, NOT_FOUND for an unresolved identity), so nothing has to be
re-aligned by hand.
The summary is always fully serialised, zeros included:
{
"prohibited": 0,
"restricted": 5,
"authorised_colorant": 0,
"authorised_preservative": 2,
"authorised_uv_filter": 4,
"not_listed": 4,
"error": 1
}
"prohibited": 0 is information — no prohibited substance in this list — and an
agent that had to infer it from a missing key would be inferring from silence.
Two deliberate non-behaviours: the input order is preserved but never interpreted (an INCI list is conventionally written in descending concentration order, and exploiting that convention would be inventing concentrations), and there is no de-duplication. This is not a formulation compliance verdict: no concentration in the finished product is computed, and nothing is concluded about the product as a whole.
Where it sits in the x402 loop
No account, no key, no contract review precedes the first call:
- Discover the endpoint in the live catalog, call it, receive the
402challenge. - Pay — sign the chosen rail and replay the request.
- Read —
verdict, every entry with its Annex, status, concentrations and label wording,version.consolidated_ref, and theas_ofthat dates all of it. - Branch — clear the ingredient, print the mandatory wording, or stop the formulation and hand a human the CELEX reference.
The Quickstart walks the full discover → 402 → pay → replay
cycle with runnable snippets. Price and accepted rails are served live by the
catalog, never pinned in an article.
Freshness, and why staleness is never a 5xx
Answers come from a snapshot of the consolidated Regulation published on
EUR-Lex, dated by the version in force at the queried date rather than by the
ingestion date — the regulatory version is the answer. 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
The same dated-verdict discipline covers the neighbouring regimes:
GET /label/chem/classify— harmonised CLP classification of a substance, versioned by ATP. The CMR classifications it serves are what feed the cosmetics Omnibus amendments in the first place: a substance classified CMR under CLP is the input to the next Annex II entry.GET /label/food/additive— whether an additive is authorised in a given food category, and at which level.
Both answer from a dated publication rather than from recall — the only property that makes any of them safe to put behind an autonomous agent.
For the full field reference and error codes, see the
GET /label/cosmetic/ingredient documentation;
for how agents discover and call Invoket endpoints, see
For agents.