GET /label/food/additive
Answers the only question the Union list actually answers: is this additive authorised in this food category, at what maximum level and under which conditions, under Regulation (EC) No 1333/2008 (Annex II — the Union list by food category — and Annex III). Maximum levels, restrictions and footnotes are served word for word, and the answer is versioned by the consolidated text in force at the queried date. The lookup is local: milliseconds, no network call and no secret.
Use it when an agent validates a recipe, a food label or an import — “may I use
E 250 in heat-treated meat products, and at what level?”. See the live
/catalog for the authoritative endpoint listing and price.
An additive has no status in the abstract
This is the nuance that decides whether an answer means anything. An authorisation is published for a couple: (additive, food category). There is no such thing as an “authorised additive” — only an additive authorised in a given category, at a level published for that category.
The endpoint enforces it in its own response shape:
- with
food_category→ you get averdict(authorised/not_authorised) and theauthorisations[]published for that couple; - without
food_category→ there is noverdictfield at all. You getauthorised_in— an inventory of the categories where an authorisation exists ({ total, truncated, categories[] }, capped at 100) — and nothing more.
An inventory is not a claim. Any answer that ignores the food category is wrong, and this service refuses to produce one.
The second property to read is that the Union list is positive: a couple with
no published authorisation is not_authorised at the queried date. That
verdict is dated and is not a safety assessment.
x402 golden rule: the agent pays for the answer to its question. Both
authorised and not_authorised are billed 200s — under a positive list,
“no published authorisation for this couple at this date” is the information.
So is a no-category inventory. Only requests the service cannot answer — zero or
both keys, an off-template E number, an unknown additive, an unknown category
code, a date before the oldest ingested version — leave the 200 range and are
not billed.
Parameters
Exactly one of the two identity keys, as a query parameter.
| Parameter | Type | Required | Description |
|---|---|---|---|
e_number | string | one of the two | E number, template E + 3 or 4 digits + optional letter and roman numeral (E 160a(i)) — validated before any lookup |
name | string | one of the two | Exact published name of the additive |
food_category | string | no (see above) | Food category code as published (08.3.2); Annex III parts are exposed as III.1 to III.5 |
date | string | no | YYYY-MM-DD — serves the consolidated version in force at that date (defaults to today) |
GET /label/food/additive?e_number=E250&food_category=08.3.2
GET /label/food/additive?e_number=E250
An off-template E number is rejected before any lookup; a code outside the
referential is a 400 that names where the codes come from; a date earlier
than the oldest ingested version is a 400 naming the bound.
200 response — UnifiedResponse
| Field | Type | Description |
|---|---|---|
verdict | string | authorised or not_authorised — present only when food_category was provided |
additive | object | e_number, e_number_published, name, synonyms, functional_classes, groups |
food_category | object | code, name, parent_code, annex — the resolved category (with a verdict only) |
authorisations | object[] | One per published line covering the couple (see below); empty on not_authorised |
authorised_in | object | No-category form only: { total, truncated, categories[] }, capped at 100 |
version | object | The consolidated version served: consolidated_ref, applicable_from, applicable_to |
note | string | Why the verdict reads as it does — a dated not_authorised statement, or the group resolution behind an authorised |
limits | string[] | The fixed caveats carried by every response (a sixth one is added on the no-category form) |
An authorisations[] entry
| Field | Type | Description |
|---|---|---|
annex | string | II (Union list by food category) or III |
part | string | Part of the Annex |
max_level | string | Word for word — "100", "500 mg/kg", "quantum satis"; never converted to a number |
restrictions | string | Restrictions of use as published, line breaks and periods of application included |
published_as | string | The wording the line is published under ("Nitrites", "Additives") when it is not the additive’s own name |
footnotes | object[] | { number, text } — verbatim |
via_group | object | Present when the authorisation is published for a group rather than for the additive by name |
amending_act | string | The amending regulation, with amending_celex and amending_oj_date |
quantum satis is served as published, never turned into a number: it is a
regulatory instruction (“no maximum level, use no more than needed”), not a
missing value to be filled in.
via_group — group authorisations resolved at read time
Many authorisations are published for a group (Group I to IV, or a combined
range such as E 249 - 250) rather than for the additive by name. Those are
stored as published and resolved when you ask, with via_group reporting the
resolution (group_name, group_key, and specific_max_level when the group
line carries one). A range whose composition is not published in Part C cannot be
resolved — not_authorised then means no published authorisation was found for
this couple, which is exactly what it says.
Example — a verdict, with the group resolution stated
GET /label/food/additive?e_number=E250&food_category=08.3.2 — sodium nitrite in
heat-treated meat products. The authorisation is published for the group
E 249-250, and the response says so instead of pretending E 250 is named:
{
"data": {
"verdict": "authorised",
"additive": {
"e_number": "E250",
"e_number_published": "E 250",
"name": "Sodium nitrite",
"functional_classes": ["other than colours and sweeteners"],
"groups": ["E 249–250"]
},
"food_category": {
"code": "08.3.2",
"name": "Heat-treated meat products",
"parent_code": "08.3",
"annex": "II"
},
"authorisations": [
{
"annex": "II",
"part": "E",
"max_level": "55",
"restrictions": "only sterilised meat products (Fo > 3,00)\nPeriod of application: from 9 October 2025",
"published_as": "Nitrites",
"footnotes": [
{
"number": "58",
"text": "(58): Fo-value 3 is equivalent to 3 minutes heating at 121 °C (reduction of the bacterial load of one billion spores in each 1 000 cans to one spore in a thousand cans)"
}
],
"via_group": { "group_name": "E 249-250", "group_key": "E249-250" },
"amending_act": "COMMISSION REGULATION (EU) 2023/2108 of 6 October 2023",
"amending_celex": "32023R2108",
"amending_oj_date": "2023-10-09"
}
],
"version": {
"consolidated_ref": "02008R1333 — EN — 18.02.2026",
"applicable_from": "2026-02-18",
"applicable_to": "2026-08-18"
},
"note": "E 250 is not authorised by name in food category 08.3.2: the authorisation is published for the group(s) E 249 - 250, of which it is a published member. The group reference is kept as published and resolved at read time."
},
"provenance": {
"source": "Regulation (EC) No 1333/2008 on food additives — consolidated text (EUR-Lex), Annex II (Union list by food category) and Annex III",
"fetched_at": "2026-07-30T18:28:58Z",
"freshness": { "kind": "snapshot", "as_of": "2026-02-18T00:00:00Z" }
}
}
The real answer carries several authorisations[] lines for this couple, each
with its own period of application — the level dropped from "100" to
"55" on 9 October 2025, and both lines are served with the restriction text
that dates them. Reading the level without its restrictions loses the date.
Example — no food category, therefore no verdict
GET /label/food/additive?e_number=E250 returns no verdict field, only the
inventory of categories where an authorisation exists:
{
"additive": { "e_number": "E250", "name": "Sodium nitrite", "groups": ["E 249–250"] },
"authorised_in": {
"total": 7,
"truncated": false,
"categories": [
{ "code": "08.2", "name": "Meat preparations as defined by Regulation (EC) No 853/2004", "via_group": "E 249–250" },
{ "code": "08.3.1", "name": "Non-heat-treated meat products", "via_group": "E 249–250" },
{ "code": "08.3.2", "name": "Heat-treated meat products", "via_group": "E 249-250" },
{ "code": "III.3", "name": "Food additives including carriers in food enzymes" }
]
}
}
The response adds a sixth limit spelling out the contract: “No verdict is issued without ‘food_category’: ‘authorised_in’ lists the food categories in which an authorisation is published for this additive, and nothing more. An additive is never ‘authorised’ as such.”
Example — not authorised is a dated, billed 200
GET /label/food/additive?e_number=E250&food_category=01.1 (milk):
{
"verdict": "not_authorised",
"food_category": {
"code": "01.1",
"name": "Unflavoured pasteurised and sterilised (including UHT) milk",
"parent_code": "01",
"annex": "II"
},
"authorisations": [],
"note": "No authorisation is published for E 250 in food category 01.1 (Unflavoured pasteurised and sterilised (including UHT) milk) in the Union list of Regulation (EC) No 1333/2008 as in force on 2026-07-30 (consolidated version 02008R1333 — EN — 18.02.2026 of the Regulation). Under the positive list, this means the additive may not be used in that food category at that date."
}
Example — quantum satis, and an Annex III part
GET /label/food/additive?e_number=E300&food_category=01.4 (ascorbic acid) is
authorised through Group I, whose level is quantum satis — served as the
phrase, on the group line:
"authorisations": [
{
"annex": "II",
"part": "E",
"published_as": "Additives",
"via_group": {
"group_name": "Group I",
"group_key": "GROUPI",
"specific_max_level": "quantum satis"
},
"amending_act": "COMMISSION REGULATION (EU) No 1129/2011 of 11 November 2011"
}
]
Annex III parts are queried with the same key shape
(food_category=III.3 → Food additives including carriers in food enzymes),
and answer with "annex": "III" and the level published there
("max_level": "500 mg/kg").
What this endpoint refuses to do
The limits are content, not a footer:
- an additive has no status in the abstract — an authorisation is a couple, and any answer ignoring the food category is wrong;
- the Union list is positive:
not_authorisedis dated and is not a safety assessment; - levels, restrictions and footnotes are served exactly as published
(
quantum satisincluded); - group authorisations are stored as published and resolved at read time with
via_group; an unpublished composition is not expanded; - out of scope: flavourings (Regulation (EC) No 1334/2008), food enzymes (No 1332/2008), novel foods, allergen labelling, the nutrition declaration, acceptable daily intakes, and the level of an additive in a finished product (no dose computation, no cumulation).
Freshness
Answers are served from a snapshot of the consolidated Regulation, dated by
the version in force at the queried date. A missed refresh keeps serving the last
good snapshot, honestly dated by freshness.as_of, and never becomes a 5xx.
Errors
Only requests the service cannot answer leave the 200 range — none of them are billed.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | Zero or both identity keys; an off-template E number; an unknown or empty food_category; a date before the oldest ingested version |
| 404 | NOT_FOUND | The additive is absent from the Union list (Annexes II and III) |
| 503 | DATA_UNAVAILABLE | The food-additives dataset has not been ingested yet |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "provide exactly one of 'e_number' or 'name'", "code": "INVALID_INPUT" }
{ "error": "invalid E number format: 'E25' (expected E followed by 3 or 4 digits, an optional letter and an optional roman numeral, e.g. 'E 160a(i)')", "code": "INVALID_INPUT" }
{ "error": "unknown food category code: '99.9' (use a category code as published in Annex II Part D of Regulation (EC) No 1333/2008, e.g. '08.3.2')", "code": "INVALID_INPUT" }
{ "error": "food additive not found in the Union list of Regulation (EC) No 1333/2008 (Annexes II and III)", "code": "NOT_FOUND" }
An unknown additive is a 404 (there is nothing to answer about); a known
additive with no authorisation in the requested category is a billed
200 not_authorised. The two are never confused.
Attribution
Verdicts are derived from Regulation (EC) No 1333/2008 on food additives, consolidated text published on EUR-Lex (European Union) — Annex II (the Union list by food category) and Annex III.
See also
GET /label/cosmetic/ingredient— the same discipline for cosmetic ingredients under Regulation (EC) No 1223/2009.GET /label/claims/check— whether a nutrition or health claim is authorised, rejected or absent from the register.- For agents — discovery surfaces, the live
/catalogand how settlement works.