# ECHA REACH Lookup – SVHC, Annex XIV & XVII Status (`lwsdjfls/echa-reach-svhc-lookup`) Actor

REACH substance lookup on ECHA CHEM: CAS number, EC number or name in; registration status, SVHC candidate list inclusion with date and reason, Annex XIV and Annex XVII status and harmonised classification out. Monitor mode reports every Candidate List addition on the day.

- **URL**: https://apify.com/lwsdjfls/echa-reach-svhc-lookup.md
- **Developed by:** [Leyten Data](https://apify.com/lwsdjfls) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $200.00 / 1,000 substance looked ups

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

### What does ECHA REACH Lookup do?

**REACH substance lookup with SVHC candidate list status** for manufacturers, importers, regulatory affairs and product compliance teams: look up any substance on **ECHA CHEM** by CAS number, EC number or name and get, in one record, its REACH registration status, whether it is on the Candidate List of substances of very high concern and since when, its Authorisation List (Annex XIV) and Restriction List (Annex XVII) status, and its harmonised classification (CLP Annex VI). Switch to **monitor mode** and every Candidate List addition reaches you the day ECHA publishes it, per substance and per list, with the reason for inclusion.

#### A missed Candidate List addition is a breach of the information duty

The Candidate List is updated by ECHA once or twice a year (28 October 2008 to 4 February 2026: 253 entries). Each addition starts legal clocks for everyone who makes, imports or sells articles containing that substance above 0.1% weight by weight:

- **Article 33 REACH**: suppliers of such articles must give their customers enough information for safe use, at least the name of the substance, **immediately** after the inclusion; consumers may ask for it and must be answered within 45 days.
- **Article 7(2) REACH**: producers and importers of such articles must notify ECHA within six months if the substance is present above one tonne per year.
- **Article 9(1)(i) of the Waste Framework Directive**: suppliers of such articles must notify them in ECHA's SCIP database.
- **Article 31 REACH**: safety data sheets must mention the inclusion; downstream users receive updated SDSs.

Nobody sends a letter. ECHA publishes the update on its website and the obligations apply from that day. A company that learns of an inclusion at the next annual review has been out of compliance for months; market surveillance authorities check Article 33 information actively, and non-compliance is an offence in every member state.

This Actor does two things about that: it answers, for any substance, *what is its status today*, and it watches the lists so that *what changed* arrives as a dataset item, a webhook and, if you wish, an e-mail, on the day.

#### Monitor mode: the lists, and your substances

Set `mode` to `monitor`, choose the lists to follow, add the substances of your portfolio, and schedule the task (daily is right for the Candidate List; ECHA publishes updates on working days).

- The **first run records a baseline** of every entry of the followed lists and the current status of every watched substance. Baseline entries are delivered as `baseline` records so you can see what is being watched; they are free. Each watched substance is looked up once, at the lookup price.
- **Every later run delivers only what changed**: a substance **added** to a followed list (with its date of inclusion and reasons), an entry that **changed** (a sunset date, a group entry that gained members, a restriction amended) or one that was **removed**, and a watched substance whose status changed (it entered the Candidate List, got an Annex XIV entry, became restricted, lost its registration, gained a harmonised classification). A run in which nothing changed costs the start fee only.
- The `summary` of each change is written for a compliance inbox: `n-hexane (110-54-3): now on the Candidate List (included 2026-02-04; Specific target organ toxicity after repeated exposure (Article 57(f) - human health))`.
- The `webhookUrl` receives a POST with the run's summary and the changes. Point it at your ticketing system, Slack or an e-mail bridge.

Followed lists:

| Key | List | ECHA CHEM page |
| --- | --- | --- |
| `candidateList` | Candidate List of substances of very high concern (SVHC) | <https://chem.echa.europa.eu/obligation-lists/candidateList> |
| `authorisationList` | Authorisation List (REACH Annex XIV) | <https://chem.echa.europa.eu/obligation-lists/authorisationList> |
| `restrictionList` | Restriction List (REACH Annex XVII) | <https://chem.echa.europa.eu/obligation-lists/restrictionList> |

#### Sources

Everything comes from **ECHA CHEM** (`chem.echa.europa.eu`), the European Chemicals Agency's chemicals database, through the same JSON interface its own web application uses: substance search, the regulatory lists, per-substance obligations, harmonised classifications and REACH registration dossiers. There is no full-database download for these lists, and ECHA's legal notice prohibits systematic copies of its databases; this Actor makes only the requests needed for your lookups and pages through a followed list once per run, paced at four requests per second.

- Reuse of ECHA data is permitted for commercial purposes with attribution. Records delivered by this Actor should carry: *Source: European Chemicals Agency, https://echa.europa.eu/*.
- CAS Registry Numbers are the property of the American Chemical Society; ECHA provides them so that regulatory requirements can be fulfilled, which is what this Actor uses them for.
- **SCIP** (the database of articles containing Candidate List substances) is not included: ECHA offers no interface to it and its dissemination has been delayed since 2026. The Actor tells you *that* a substance requires a SCIP notification; it does not read SCIP.
- ECHA CHEM also holds the EUCLEF cross-references to other EU legislation; they are counted (`otherLegislationReferences`) but not delivered, as their terms exclude resale.

### Features

- **SVHC candidate list check** per substance: listed or not, direct or group entry, date of inclusion and Article 57 reasons
- **Authorisation List (Annex XIV) status** with sunset date, latest application date and remarks
- **Restriction List (Annex XVII) entries** that cover the substance directly or through a group, with the amending regulation and the link to the conditions
- **REACH registration status**: active and inactive dossiers, full versus intermediate registrations, tonnage band, latest update
- **Harmonised classification (CLP Annex VI)**: hazard classes, H-statements, signal word, pictograms and a `cmrCategory1` flag
- **CAS and EC number check-digit validation** before anything is searched, and exact-name matching so a lookup never returns the wrong substance
- **Candidate List monitor**: every addition, change or removal on the followed lists on the day ECHA publishes it, with a webhook
- **Live from ECHA CHEM**: every field is what ECHA says at the moment of the run, never a copy
- Runs through the Apify API, on a schedule, or from Make, Zapier and n8n

### Use cases

#### Manufacturers and importers of articles: the Article 33 check

Put the substances of your bill of materials (CAS numbers from the supplier declarations) in `substances`, run once for the current picture, then switch the task to monitor mode and schedule it. `candidateList.listed` with `dateOfInclusion` is the Article 33 trigger; `restrictionList.entries` tells you whether an article may be placed on the market at all; `authorisationList.sunsetDate` is the day a use without authorisation becomes illegal.

#### Regulatory affairs: portfolio screening

A thousand substances cost $200 and take about ten minutes. Sort the dataset by `harmonisedClassification.cmrCategory1`, `candidateList.listed` and `registration.status` and you have the substitution shortlist and the list of substances whose registrations are inactive (a supplier whose registration lapsed cannot lawfully supply above one tonne).

#### Product compliance and PLM software

Call the Actor from your backend when a new material is created, and store the record with the material:

```js
// npm install apify-client
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lwsdjfls/echa-reach-svhc-lookup').call({ substances: ['117-81-7', 'EC 203-777-6', 'Bisphenol A'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems({ clean: true });

for (const { payload } of items) {
    if (payload.status !== 'found') { console.warn(payload.input, payload.status, payload.statusReason); continue; }
    const flags = [
        payload.candidateList.listed && `SVHC since ${payload.candidateList.dateOfInclusion}`,
        payload.authorisationList.listed && `Annex XIV, sunset ${payload.authorisationList.sunsetDate}`,
        payload.restrictionList.listed && `Annex XVII entry ${payload.restrictionList.entries.map((e) => e.entryNumber).join('/')}`,
        payload.harmonisedClassification?.cmrCategory1 && 'CMR 1A/1B',
    ].filter(Boolean);
    console.log(`${payload.substance.name} (${payload.substance.casNumber}): ${flags.join('; ') || 'no listing'}`);
}
```

#### Distributors and e-commerce: restricted substances in consumer articles

Annex XVII limits what may be in toys, textiles, jewellery, packaging and consumer mixtures. Look up the substances your suppliers declare and act on `restrictionList.entries`; each entry links to the conditions in EUR-Lex.

### How to use it

1. Keep **Mode** on `lookup` and paste what your suppliers gave you into **Substances**: CAS numbers, EC numbers or plain substance names, mixed freely. A bill of materials pasted as one column per line is the normal starting point. Each entry becomes one record, including the ones ECHA does not know.
2. Read the `substances` view. Per substance you get registration status, whether it sits on the Candidate List with the date and the reason it was added, its Annex XIV authorisation and Annex XVII restriction status, and the harmonised classification when **Include harmonised classification** is on.
3. Switch **Mode** to `monitor`, give it a **State key** and schedule it. The Candidate List is updated in batches, usually twice a year but not on a fixed date, and the obligation to inform your customers starts the day a substance is added. A scheduled monitor with a webhook tells you that day instead of at your next manual review.

The `changes` view is what a monitor produces, and `listEntries` is the list itself if you would rather hold a copy. A substance that ECHA answers only partially still produces a record with the fields it did return, so a half answer is visible rather than missing.

Know where the answer stops. This reports what ECHA CHEM publishes about a substance. It does not know your product, your concentrations or your tonnage, so it cannot tell you whether an article of yours crosses the 0.1 percent threshold or whether you need an authorisation. It gives a compliance team the regulatory status per substance, quickly and with the dates attached, and the assessment stays theirs.

```json
{
  "mode": "lookup",
  "substances": ["117-81-7", "EC 203-777-6", "Bisphenol A"],
  "includeClassification": true
}
```

### Input

| Field | What it does | Example |
| --- | --- | --- |
| `mode` | `lookup` delivers every substance on every run; `monitor` records a baseline and later delivers only changes. | `"lookup"` |
| `substances` | One per line: a CAS number, an EC number (`204-211-0` or `EC 204-211-0`) or a substance name. Numbers are check-digit validated; names must equal a name ECHA knows. Lookups that find nothing are free, so they share an allowance of 1,000 requests per run, retries included, plus `maxSearchPages` per substance found: a lookup starts only while its search requests (one per CAS or EC number, up to `maxSearchPages` per name) still fit next to those of the lookups in progress, which can take a run past the allowance with their retries and, when they fail, the requests after their search. The substances that do not fit are logged as not looked up. CAS and EC numbers are looked up, and listed, before names. | `["117-81-7", "EC 203-777-6", "Bisphenol A"]` |
| `lists` | Regulatory lists whose entries are followed in monitor mode. | `["candidateList", "authorisationList", "restrictionList"]` |
| `includeClassification` | Adds the harmonised classification and labelling (CLP Annex VI). | `true` |
| `includeRegistrations` | Adds the REACH registration status. | `true` |
| `maxSearchPages` | How many pages of 100 search results are scanned for an exact name match ("Bisphenol A" is on page 2). | `3` |
| `maxSubstancesPerRun` | Caps the charged lookups in one run. In lookup mode a run also starts at most twice this many lookups, because unknown numbers and unmatched names are free but still take requests; the rest is logged as not looked up. | `1000` |
| `maxChangesPerRun` | Monitor mode: caps the charged changes in one run. When more change at once, delivery stops at the cap and the rest follows in the next runs. | `25` |
| `maxConcurrency` | Substances looked up at the same time; requests to ECHA CHEM are paced at 4 per second regardless. | `3` |
| `webhookUrl` | Receives a POST with a summary after each run that delivered results or changes; must be a public http(s) address and is stored encrypted. | `https://hooks.example.com/reach` |
| `stateKey` | Separates independent monitors. Defaults to the task ID, so each saved task keeps its own baseline. | `portfolio-2026` |

```json
{
  "substances": ["117-81-7", "EC 203-777-6", "Bisphenol A", "7439-92-1"],
  "includeClassification": true,
  "includeRegistrations": true
}
```

A CAS number (`117-81-7`), an EC number (`204-211-0` or `EC 204-211-0`) or a name, one per line. Numbers are validated against their check digit before anything is searched, because a mistyped CAS number silently finds a different substance. Names must equal a name ECHA holds for exactly one substance (ECHA's own search ranks "Bisphenol A" 127th of 225 results; the Actor scans the pages and picks the record that carries the name). When several records carry the name, the result is `ambiguous`, the candidates are listed with their CAS numbers, and nothing is charged.

Monitor mode:

```json
{
  "mode": "monitor",
  "lists": ["candidateList", "authorisationList", "restrictionList"],
  "substances": ["117-81-7", "110-54-3", "80-05-7", "7439-92-1"],
  "webhookUrl": "https://hooks.example.com/reach"
}
```

### Output

One record per substance, CAS and EC numbers first, then names. In monitor mode, one record per change (`changeType` `added`, `changed` or `removed`) with structured `changes` and a `summary`. A monitor run that could read none of its followed lists and watched substances (ECHA CHEM not answering, or a spending limit too low for the first lookup) ends as failed, charges nothing and keeps the recorded state, so the next run still reports every change since the last successful one. A watched number with a wrong check digit needs no answer from ECHA CHEM, so with one in the list such a run can end as partial instead.

- `status`: `found`, `not-found` (ECHA has no substance under that number), `ambiguous` (several substances carry that name; the candidates are listed), `invalid-identifier` (wrong CAS or EC check digit) or `lookup-failed` (ECHA CHEM did not answer completely, or had stopped answering before the substance was looked up; nothing charged)
- `substance`: ECHA's identity of the record: name, EC number, CAS number, other CAS numbers ECHA associates with it, IUPAC name, molecular formula, CLP index number, synonyms
- `registration`: `registered`, `inactive-registrations-only` (ECHA holds dossiers but none is active, so supplying above one tonne per year is not allowed: "no data, no market") or `not-registered`, with the number of active and inactive dossiers, full (Article 10) versus intermediate (Article 17/18) registrations, joint submission, tonnage band and the date of the latest dossier update
- `candidateList`: listed yes/no, direct entry or group entry, the entry's name, **date of inclusion** and **reasons** (Article 57 criteria: CMR, PBT, vPvB, endocrine disruption, equivalent concern)
- `authorisationList`: listed yes/no, Annex XIV entry number, **sunset date**, latest application date, reasons and the entry's remarks (exempted uses, transitional arrangements)
- `restrictionList`: every Annex XVII entry that covers the substance, directly or through a group (the phthalates, the PFAS, the CMR entries), with the entry number, the amending regulation and the link to its conditions
- `harmonisedClassification`: index number, hazard classes and categories (`Repr. 1B`, `Carc. 1A`, `STOT RE 1`), H-statements, signal word, pictograms, the ATP that set them, the date they apply from, and `cmrCategory1` (true for a category 1A/1B CMR)
- `otherProcesses`: further ECHA processes the substance is in (substance evaluation under CoRAP, PIC, biocides, POPs, Drinking Water Directive) and the number of other EU legislation references ECHA holds for it
- `sourceUrl` to the ECHA CHEM record, and `sourceCheckedAt`

Every field comes from ECHA CHEM at the moment of the run, not from a copy: the answer for a substance is what ECHA says right now.

```json
{
  "envelopeVersion": 1,
  "id": "cas:117-81-7",
  "scope": "lookup:cas:117-81-7",
  "changeType": "added",
  "sourceUrl": "https://chem.echa.europa.eu/100.003.829",
  "scrapedAt": "2026-09-15T12:00:00.000Z",
  "firstSeenAt": "2026-09-15T12:00:00.000Z",
  "hash": "faef803e25fbf3e0680ee22fd11d4c0938b307a057edb984b982abfaf4270979",
  "previousHash": null,
  "changes": [],
  "summary": "Added: Bis(2-ethylhexyl) phthalate (117-81-7)",
  "diffText": null,
  "payload": {
    "kind": "substance",
    "input": "117-81-7",
    "identifierType": "cas",
    "status": "found",
    "statusReason": null,
    "substance": {
      "rmlId": "100.003.829",
      "name": "Bis(2-ethylhexyl) phthalate",
      "ecNumber": "204-211-0",
      "casNumber": "117-81-7",
      "otherCasNumbers": [
        "109630-52-6",
        "137718-37-7",
        "126639-29-0",
        "8033-53-2",
        "50885-87-5",
        "607374-50-5",
        "40120-69-2",
        "205180-59-2",
        "275818-89-8"
      ],
      "iupacName": null,
      "molecularFormula": "C24H38O4",
      "indexNumber": "607-317-00-9",
      "synonyms": [
        "DEHP",
        "di-sec-octyl phthalate",
        "Phthalic acid di(2-ethylhexyl) ester; DDT Dioctylphthalate; DEHP",
        "Di-2-ethylhexyl phthalate",
        "Bis(3-methylhexyl)benzene-1,2-dicarbonate",
        "DEHP or DI-(2-ETHYLHEXYL)PHTHALATE or BIS-(2-ETHYLHEXYL)PHTHALATE",
        "o-Benzenedicarboxylic acid, dioctyl ester",
        "AI3-04273",
        "Di-s-octyl phthalate, >1 - 25% in a non hazardous diluent",
        "Di-(2-ethylhexyl) phthalate (DEHP)",
        "Dioctyl phthalate (VAN)",
        "Di(2-ethylhexyl) o-phthalate",
        "1,2-Benzenedicarboxylic acid, bis(2-ethylhexyl) ester (Conc≥0.5%)",
        "di(2-ethylhexyl)phthalate; bis(2-ethylhexyl) phthalate",
        "Phthalic acid dioctyl ester",
        "1,2-Benzenedicarboxylic acid, 1,2-bis(2-ethylhexyl) ester (C < 0.1%)",
        "1,2-Benzenedicarboxylic acid bis(2-ethyl-hexyl) ester; Bis(2-ethylhexyl)phthalate, Dioctyl phthalate",
        "BEHP",
        "1,2-Benzenedicarboxylic acid, bis(2-ethylhexyl) ester (List 1-Inert Ingredient of Toxicological Concern)",
        "Bis(ethylhexyl) phthalate"
      ]
    },
    "registration": {
      "status": "registered",
      "activeDossiers": 11,
      "inactiveDossiers": 17,
      "totalDossiers": 28,
      "fullRegistrations": 9,
      "intermediateRegistrations": 2,
      "jointSubmission": true,
      "tonnageBand": "tonnageBand10000to100000",
      "tonnageBandLabel": "10,000 – 100,000 tonnes per year",
      "latestDossierUpdate": "2024-07-24",
      "preRegistered2008": true
    },
    "candidateList": {
      "listed": true,
      "entryType": "direct",
      "entryName": "Bis(2-ethylhexyl) phthalate",
      "entryRmlId": "100.003.829",
      "dateOfInclusion": "2008-10-28",
      "reasons": [
        "Toxic for reproduction (Article 57c)",
        "Endocrine disrupting properties (Article 57(f) - environment)",
        "Endocrine disrupting properties (Article 57(f) - human health)"
      ],
      "sourceUrl": "https://chem.echa.europa.eu/100.003.829/obligations/candidateList/details"
    },
    "authorisationList": {
      "listed": true,
      "entryType": "direct",
      "entryName": "Bis(2-ethylhexyl) phthalate (DEHP)",
      "entryNumber": "04",
      "sunsetDate": "2015-02-21",
      "latestApplicationDate": "2013-08-21",
      "reasons": [
        "Toxic for reproduction (Article 57c)",
        "Endocrine disrupting properties (Article 57(f) - environment)",
        "Endocrine disrupting properties (Article 57(f) - human health)"
      ],
      "remarks": "1 September 2021 for the use of the substance in the production of spare parts as articles or as complex products for the repair of articles or complex products, the production of which ceased or will have ceased before the sunset date indicated in the entry for that substance, where that substance was used in the production of those articles or complex products and these cannot function as intended without that spare part and the spare part cannot be produced without that substance, and for the use of the substance (on its own or in a mixture) for the repair of such articles or complex products where that substance on its own or in a mixture was used in the production of those articles or complex products and they cannot be repaired otherwise than by using that substance.\n\nBy way of derogation:\n14/06/2023 for uses in:\n- food contact materials within the scope of Regulation (EC) No 1935/2004,\n- immediate packaging of medicinal products covered under Regulation (EC) No 726/2004, Directive 2001/82/EC, and/or Directive 2001/83/EC,\n- mixtures containing DEHP at or above 0,1 % and below 0,3 % weight by weight;\n\n01/01/2029 for uses in medical devices within the scope of Regulations (EU) 2017/745 and (EU) 2017/746",
      "sourceUrl": "https://chem.echa.europa.eu/100.003.829/obligations/authorisationList/details"
    },
    "restrictionList": {
      "listed": true,
      "entries": [
        {
          "entryNumber": "51",
          "entryName": "Diisobutyl phthalate (DIBP); Dibutyl phthalate (DBP); Benzyl butyl phthalate (BBP); Bis(2-ethylhexyl) phthalate (DEHP)",
          "entryType": "group",
          "regulation": "R 2018/2005",
          "conditionsUrl": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv:OJ.L_.2018.322.01.0014.01.ENG&toc=OJ:L:2018:322:TOC",
          "decisionDates": [
            "2005-12-14",
            "2009-06-22",
            "2015-03-02",
            "2018-12-17"
          ],
          "sourceUrl": "https://chem.echa.europa.eu/100.003.829/obligations/restrictionList/details"
        }
      ]
    },
    "harmonisedClassification": {
      "listed": true,
      "indexNumber": "607-317-00-9",
      "chemicalName": "bis(2-ethylhexyl) phthalate; di-(2-ethylhexyl) phthalate; DEHP",
      "atp": "CLP00",
      "effectiveDate": "2010-12-01",
      "status": "In application",
      "signalWord": "Danger",
      "hazardClasses": [
        {
          "code": "Repr. 1B",
          "hazardStatements": [
            {
              "code": "H360FD",
              "text": "May damage fertility. May damage the unborn child."
            }
          ]
        }
      ],
      "hazardStatements": [
        "H360FD"
      ],
      "pictograms": [
        {
          "code": "GHS08",
          "text": "Health hazard"
        }
      ],
      "cmrCategory1": true,
      "sourceUrl": "https://chem.echa.europa.eu/100.003.829/harmonised/424025"
    },
    "otherProcesses": [
      {
        "code": "PIC_INVENTORY",
        "label": "PIC inventory"
      },
      {
        "code": "CNL_NOTIFIED",
        "label": "C&L Inventory notifications"
      },
      {
        "code": "DWD_INVENTORY",
        "label": "Drinking Water Directive inventory"
      },
      {
        "code": "RSIM_DWD_INVENTORY",
        "label": "Drinking Water Directive inventory"
      },
      {
        "code": "PIC_ANNEX_I",
        "label": "PIC Annex I"
      }
    ],
    "otherLegislationReferences": 31,
    "candidates": [],
    "sourceUrl": "https://chem.echa.europa.eu/100.003.829",
    "sourceCheckedAt": "2026-09-15T12:00:00.000Z"
  }
}
```

### Pricing

| Event | Name | What it means | Price |
| --- | --- | --- | --- |
| `apify-actor-start` | Actor start | Charged by Apify when a run starts: once for a run of up to 1 GB of memory, and once more for every extra GB. It covers the first five seconds of compute. | $0.00005 per run up to 1 GB of memory ($0.05 per 1,000) |
| `substance-looked-up` **(primary)** | Substance looked up | One substance resolved in ECHA CHEM and delivered with its identity, REACH registration status, Candidate List, Authorisation List and Restriction List status, and harmonised classification. In monitor mode charged once, when a watched substance is first recorded. | $0.20 per substance ($200.00 per 1,000) |
| `change-detected` | Change detected | In monitor mode: a substance added to, changed on or removed from a followed list (Candidate List, Authorisation List, Restriction List), or a change in the status of a watched substance. | $1.00 per change ($1000.00 per 1,000) |

The example input above (four substances) costs $0.80. A portfolio of 1,000 substances costs $200, once. Following the Candidate List, Authorisation List and Restriction List daily costs the Actor start fee per run (a fraction of a cent) plus $1 per change; the Candidate List gained 2 entries in February 2026 and 1 in November 2025, the Authorisation List and Restriction List change a few times a year. One run never charges more than `maxChangesPerRun` changes (default 25). A missed inclusion costs a great deal more.

Not charged: substances ECHA does not know, ambiguous names, invalid numbers, lookups that failed halfway, baseline runs, and runs in which nothing changed. Because a lookup that finds nothing is free, such lookups share an allowance of 1,000 requests per run (in either mode), retries included, plus `maxSearchPages` per substance found. A lookup starts only while its search requests (one per CAS or EC number, up to `maxSearchPages` per name) still fit next to those of the lookups in progress, which can take a run past the allowance with their retries and, when they fail, the requests after their search. CAS and EC numbers go before names, and the substances that do not fit are logged as not looked up, so remove numbers and names ECHA CHEM does not know from long lists. When ECHA CHEM stops answering (10 lookups in a row without an answer; in lookup mode one more for each substance the run has found, up to 50), new lookups wait and one lookup tries again: it makes each of its requests up to five times, pausing between the attempts for a random time of up to 30, 60, 120 and 120 seconds (under three minutes per request on average), and at least as long as ECHA CHEM asks, if that is two minutes or less. An outage of a few minutes is often waited out, not always; if ECHA CHEM answers, the run goes on, otherwise it starts no more lookups. In lookup mode the substances it did not look up are delivered free with status `lookup-failed`: run just those again later, because a run of the whole list charges every substance it finds again. A watched substance that a run could not look up (a limit reached, ECHA CHEM not answering) is not recorded yet: the run that first looks it up records it and charges the lookup, not a change.

### FAQ

#### What is the difference between the Candidate List, the Authorisation List and the Restriction List?

The **Candidate List** (Article 59) names substances of very high concern; inclusion triggers information duties (Articles 7(2), 31, 33, SCIP) but does not forbid anything. Substances are moved from it to the **Authorisation List** (Annex XIV) with a **sunset date**, after which every use in the EU needs an authorisation. The **Restriction List** (Annex XVII) limits or bans specific uses, concentrations or articles regardless of the other two lists; a substance can be restricted without ever being on the Candidate List.

#### A substance is "listed" through a group entry. What does that mean?

Many entries cover a group ("phthalates", "PFAS", "lead compounds"). ECHA links the member substances to the entry; the Actor reports `entryType: "group"` with the entry's name, and the obligations are the same as for a direct entry.

#### Why is a name not found while ECHA's website finds it?

ECHA's search returns every record that mentions the words; the Actor accepts only records that carry the exact name (as ECHA name, EC name, IUPAC name, synonym or trade name), so that a lookup never returns the wrong substance. Use the CAS or EC number when in doubt; both are unique.

#### How soon after an ECHA update does the monitor report it?

The Actor reads ECHA CHEM live; a change is reported on the first run after ECHA publishes it. Schedule daily on working days at, say, 12:00 CET (ECHA publishes during the Helsinki working day).

#### Is an SVHC candidate list lookup legal, and does it involve personal data?

The data is public regulatory information published by ECHA for exactly this purpose; ECHA permits reuse with attribution, and no personal data is involved, so the GDPR does not apply to the output. The Actor is not affiliated with ECHA. Regulatory conclusions (whether your article needs a notification, which use is exempted) remain your responsibility; the Actor gives you ECHA's data and the links to the legal texts.

### Related Actors

- [EUDAMED Medical Device Lookup: the same live-register approach for EU medical devices](https://apify.com/lwsdjfls/eudamed-device-lookup), with monitor mode for new registrations and certificate changes.
- [EU Company ID Resolver for the suppliers behind the substances](https://apify.com/lwsdjfls/eu-company-id-resolver): VAT, KvK, KBO, SIREN and LEI with official name and status.

### Support

Found a bug or need a feature? Open an issue on the Actor page. We usually respond within a day.

# Changelog

This Actor's version history is a separate document: https://apify.com/lwsdjfls/echa-reach-svhc-lookup/changelog.md

# Actor input Schema

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

Lookup: every substance is delivered on every run with its current status. Monitor: the run records a baseline and later runs deliver only what changed on the followed lists and watched substances (schedule it).

## `substances` (type: `array`):

One per line: a CAS number (117-81-7), an EC number (204-211-0 or "EC 204-211-0") or a substance name (Bisphenol A). Numbers are check-digit validated; names must equal a name ECHA knows, otherwise the closest records are listed and nothing is charged. Lookups that find nothing are free, so they share an allowance of 1,000 requests per run, retries included, plus maxSearchPages per substance found: a lookup starts only while its search requests (one per CAS or EC number, up to maxSearchPages per name) still fit next to those of the lookups in progress, which can take a run past the allowance with their retries and, when they fail, the requests after their search. The substances that do not fit are logged as not looked up. CAS and EC numbers are looked up, and listed, before names.

## `lists` (type: `array`):

Regulatory lists whose entries are followed in monitor mode. Every addition, change or removal is delivered as a change; the first run records the current list for free.

## `includeClassification` (type: `boolean`):

Adds the harmonised classification and labelling (CLP Annex VI): hazard classes, H-statements, signal word, pictograms and whether the substance is a category 1A/1B CMR.

## `includeRegistrations` (type: `boolean`):

Adds the REACH registration status: active and inactive dossiers, full versus intermediate registrations, joint submission, tonnage band and the latest dossier update.

## `maxSearchPages` (type: `integer`):

How many pages of 100 search results are scanned for an exact name match. Common names sit deep in ECHA's ranking ("Bisphenol A" is on page 2).

## `maxSubstancesPerRun` (type: `integer`):

Caps the charged lookups in one run. In lookup mode it also bounds the work: a run starts at most twice this many lookups (numbers ECHA does not know and names without an exact match are free but still take requests); the rest is not looked up and is logged.

## `maxChangesPerRun` (type: `integer`):

Monitor mode: caps the charged changes delivered in one run. When more changed at once, delivery stops at the cap and the rest is delivered by the next runs.

## `maxConcurrency` (type: `integer`):

Substances looked up at the same time. Requests to ECHA CHEM are paced at 4 per second regardless.

## `webhookUrl` (type: `string`):

Receives a POST with a summary after each run that delivered results or changes. Must be a public http(s) address; redirects are not followed. Stored encrypted, because webhook URLs often carry a token.

## `stateKey` (type: `string`):

Separates independent monitors. Defaults to the task ID, so each saved task keeps its own baseline.

## Actor input object example

```json
{
  "mode": "lookup",
  "substances": [
    "117-81-7",
    "EC 203-777-6",
    "Bisphenol A",
    "7439-92-1"
  ],
  "lists": [
    "candidateList"
  ],
  "includeClassification": true,
  "includeRegistrations": true,
  "maxSearchPages": 3,
  "maxSubstancesPerRun": 1000,
  "maxChangesPerRun": 25,
  "maxConcurrency": 3,
  "webhookUrl": "https://hooks.example.com/reach",
  "stateKey": "portfolio-2026"
}
```

# Actor output Schema

## `substances` (type: `string`):

Status per substance: registration, Candidate List, Annex XIV, Annex XVII and CLP.

## `changes` (type: `string`):

Monitor mode: additions, changes and removals on the followed lists and watched substances.

## `listEntries` (type: `string`):

Monitor mode: the entries of the followed lists with inclusion dates, reasons and deadlines.

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

Counts, failures and the cost of the run.

# 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 = {
    "substances": [
        "117-81-7",
        "EC 203-777-6",
        "Bisphenol A",
        "7439-92-1"
    ],
    "lists": [
        "candidateList"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lwsdjfls/echa-reach-svhc-lookup").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 = {
    "substances": [
        "117-81-7",
        "EC 203-777-6",
        "Bisphenol A",
        "7439-92-1",
    ],
    "lists": ["candidateList"],
}

# Run the Actor and wait for it to finish
run = client.actor("lwsdjfls/echa-reach-svhc-lookup").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 '{
  "substances": [
    "117-81-7",
    "EC 203-777-6",
    "Bisphenol A",
    "7439-92-1"
  ],
  "lists": [
    "candidateList"
  ]
}' |
apify call lwsdjfls/echa-reach-svhc-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lwsdjfls/echa-reach-svhc-lookup"
        }
    }
}
```

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/Qx2SYMxT4beVloHik/builds/3vZJ2aKXK5OtOxZdz/openapi.json
