GET /label/chem/svhc

Resolves a substance identifier into its SVHC Candidate List status under REACH Article 59(10) (ECHA): is the substance a Substance of Very High Concern on the Candidate List — and if so, the Article 57 reasons for its inclusion, the inclusion date, the ECHA decision reference, and the generic supply-chain obligations its listing triggers (REACH Article 33 information, SCIP notification). The answer is served from a local store built from the official Candidate List, so a lookup resolves in milliseconds with no network call and no secret at request time.

Use it when an agent needs a grounded, dated, sourced verdict before placing a product or a formulation on the EU market — “is this substance on the SVHC Candidate List?”, “what are the reasons for inclusion, and what supply-chain duties does that trigger?”. This is the differentiator: the Candidate List grows ~2× a year (253 entries and counting, well past model cutoffs) with no x402 offer on this status, so an LLM answers from a stale or invented list where this endpoint answers from the last published list, honestly dated. The angle is pre-market screening — the natural sequel to /label/chem/classify. Lookup is by CAS, EC or exact name only — there is no fuzzy search in v1. See the live /catalog for the authoritative endpoint listing and price.

This endpoint returns the Candidate List entry exactly as published, and the obligations exactly as written. It is never legal advice, nor a compliance assessment of your article — the service relays the obligation an inclusion triggers, it does not decide whether that obligation applies to your specific article (the 0.1 % w/w concentration threshold, importer status, and so on). That line is the boundary of the service.

x402 golden rule: the agent pays for the answer to its question. A well-formed key the Candidate List does not carry is a successful answer too → 200 billed with verdict: "not_listed" — the absence is the information, never a 404. Only requests the service cannot answer — no key, several keys, a bad checksum, an empty name — leave the 200 range and are not billed.

Parameters

Exactly one of the three keys, as a query parameter. Zero or several keys, a bad checksum or an empty name is a 400 INVALID_INPUT. There is no at parameter: the Candidate List is not versioned by ATP, so the service serves the last published list and dates the verdict through freshness.as_of.

ParameterTypeRequiredDescription
casstringone of the threeCAS number — check-digit validated (weighted-sum mod 10, the same rigour as the IBAN mod-97)
ecstringone of the threeEC number (EINECS/ELINCS) — check-digit validated
namestringone of the threeExact Candidate List name (case and spacing folded, no fuzzy match) — the only key that resolves a UVCB with no CAS/EC
GET /label/chem/svhc?cas=84-74-2

cas and ec are validated by their check digit before any lookup — a wrong check digit is a 400 INVALID_INPUT with the rejected literal in the message, not a billed miss. An exact name is never disambiguated silently: an ambiguous name returns all of its Candidate List entries. Some SVHC are UVCB substances (of Unknown or Variable composition) with no CAS or EC number — the exact name is the only way to reach them.

200 response — UnifiedResponse

{
  "data": { "verdict": "listed", "entries": [ ... ] },
  "provenance": {
    "source": "ECHA Candidate List of SVHC (REACH Art. 59(10))",
    "fetched_at": "2026-07-24T07:45:50Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-27T00:00:00Z" }
  }
}

freshness.as_of is dated by the publication of the Candidate List served, not by the ingestion date — the dated list is the point of the answer.

Fields of data

FieldTypeDescription
verdictstringlisted (≥ 1 Candidate List entry) or not_listedboth are billed 200s
entriesobject[]The Candidate List entries as published (see below); empty on not_listed
notestringnot_listed only — the dated contractual note (see below)

A field the source does not carry is omitted from the JSON, never served null — the one exception being included_at inside an entry, a full member of the published entry (null when the inclusion date is not recorded).

An entries[] entry — the Candidate List as published

FieldTypeDescription
namestringSubstance name as published
identifiersobject[]{ ec } or { cas }relayed individually, never paired (the list does not match EC to CAS); empty [] for a UVCB with no CAS/EC
reasonsstring[]Article 57 reasons for inclusion, as published — cumulative (an entry can carry several)
included_atstringInclusion date (YYYY-MM-DD); null when not recorded
decisionstringECHA decision reference; omitted when none is published
obligationsobject{ article_33, scip } — the generic obligations the listing triggers (see below)

Identifiers are relayed exactly as published: the Candidate List lists EC and CAS numbers without a 1:1 correspondence, so each identifier is a separate { ec } or { cas } object, never paired at random. The reasons, included_at and decision are the regulatory publication, not a reformulation.

obligations — generic regulatory facts, not advice

Every listed entry carries the two supply-chain obligations that inclusion on the Candidate List triggers, served as fixed, sourced text:

FieldDescription
article_33REACH Article 33 — information in the supply chain for an article containing the substance above 0.1 % w/w
scipSCIP notification — Waste Framework Directive 2008/98/EC, Article 9(1)(i)

These are the same constants on every listed entry. The service relays the obligation an inclusion triggers; it does not compute whether that obligation applies to your article — it does not know your concentration, your importer status, or your product. Treat them as generic regulatory facts, not a compliance verdict on your case.

Example — a listed substance, resolved by CAS

GET /label/chem/svhc?cas=84-74-2 — an entry that carries several identifiers, cumulative Article 57 reasons and the full obligations:

{
  "data": {
    "verdict": "listed",
    "entries": [
      {
        "name": "example multi-identifier svhc",
        "identifiers": [
          { "ec": "201-557-4" },
          { "cas": "117-81-7" },
          { "cas": "84-74-2" }
        ],
        "reasons": [
          "Carcinogenic (Article 57a)",
          "Toxic for reproduction (Article 57c)",
          "Endocrine disrupting properties (Article 57(f) - human health)"
        ],
        "included_at": "2021-01-19",
        "decision": "ED/01/2021",
        "obligations": {
          "article_33": "REACH Article 33: a supplier of an article containing this substance in a concentration above 0.1 % weight by weight (w/w) must provide the recipient with sufficient information, available to the supplier, to allow safe use of the article, including as a minimum the name of the substance; on request, the same information must be provided to a consumer within 45 days, free of charge.",
          "scip": "SCIP notification (Waste Framework Directive 2008/98/EC, Article 9(1)(i)): suppliers of articles containing this substance in a concentration above 0.1 % weight by weight (w/w) placed on the EU market must submit information on those articles to the ECHA SCIP database."
        }
      }
    ]
  },
  "provenance": {
    "source": "ECHA Candidate List of SVHC (REACH Art. 59(10))",
    "fetched_at": "2026-07-24T07:45:50Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-27T00:00:00Z" }
  }
}

Example — a UVCB reached by its exact name

Some SVHC have no CAS or EC number (UVCB substances). They are reachable only by their exact Candidate List name, and the entry carries an empty identifiers array:

"entries": [
  {
    "name": "example uvcb reaction mass",
    "identifiers": [],
    "reasons": ["Toxic for reproduction (Article 57c)"],
    "included_at": "2018-06-27",
    "obligations": { "article_33": "...", "scip": "..." }
  }
]

The decision field is absent here because the source publishes none — an omitted field is never served as null.

Example — a well-formed key that is not listed (billed)

GET /label/chem/svhc?cas=7732-18-5 (water) — a valid CAS the Candidate List does not carry — returns a 200, billed, with a dated note in plain sight, never a 404:

{
  "data": {
    "verdict": "not_listed",
    "entries": [],
    "note": "Not on the ECHA Candidate List of SVHC as published on 2026-06-27. This means the substance was not listed at that date, not that it never will be; the Candidate List grows over time. Absence from the list does not mean the substance is of no concern."
  },
  "provenance": {
    "source": "ECHA Candidate List of SVHC (REACH Art. 59(10))",
    "fetched_at": "2026-07-24T07:45:50Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-27T00:00:00Z" }
  }
}

The agent asked “is this substance on the Candidate List?”, and “not on the list published on <as_of>” is the answer — dated, and careful never to say “never listed” or “of no concern”. The Candidate List grows over time, so the note is scoped to the served publication date. Contrast this with the 400/503 below, which are not billed.

Freshness

Answers are served from a snapshot of the Candidate List, dated by the publication of the list served (freshness.as_of), not by the ingestion date. The list is refreshed off-band from the official ECHA publication (a manual deposit, once or twice a year); if a refresh is missed, the service keeps serving the last good snapshot — honestly dated by as_of — and never turns staleness into a 5xx. A dataset not yet ingested at all is a 503 DATA_UNAVAILABLE (there is no last good snapshot to serve).

Errors

Only requests the service cannot answer leave the 200 range — none of them are billed.

StatuscodeCase
400INVALID_INPUTZero or several keys; a bad CAS/EC check digit; an empty name
503DATA_UNAVAILABLEThe Candidate List dataset has not been ingested yet — there is no last good snapshot to serve
404NOT_FOUNDUnknown route
500INTERNALInternal error (detail logged, not exposed)
{ "error": "invalid CAS check digit: '84-74-3'", "code": "INVALID_INPUT" }

A 503 for a dataset that is not yet ingested is distinct from staleness: a missed refresh serves the last snapshot, flagged by freshness.as_of, and is never a 5xx.

Attribution

Candidate List data is the ECHA Candidate List of Substances of Very High Concern for authorisation, a legal publication under REACH Article 59(10) (ECHA legal notice — commercial reuse permitted).

See also