POST /label/chem/svhc/batch

Screens a list of substances in a single call: up to 200 entries, each by cas, ec or exact name, all resolved against the last published Candidate List, settled with one x402 payment for the whole batch. Each item runs through the same engine as GET /label/chem/svhc — the SVHC Candidate List status under REACH Article 59(10). The lookup is purely local → response in milliseconds, no network call and no secret.

One settlement for N substances is the BOM / formulation screening path: an agent checks a bill of materials for Substances of Very High Concern before placing a product on the EU market. The batch is settled as one x402 payment for the whole call and priced per unit by the gateway (the first substance is roughly the price of the single call, then each additional substance adds to the total) — see the live /catalog for the authoritative price.

x402 golden rule: the agent pays for the answer to its question. A well-formed batch is a successful answer → 200, even when some substances are not_listed and even when a single item is malformed (that item is returned with { error, code } at its position, without failing the batch). The 4xx range is reserved for requests the service cannot answer (missing or malformed body, empty list, batch over the cap).

Request

POST with a JSON body. Set Content-Type: application/json. There is no batch-level date: the Candidate List is not versioned by ATP, so every item is resolved against the last published list.

POST /label/chem/svhc/batch
Content-Type: application/json
{
  "items": [{ "cas": "84-74-2" }, { "cas": "7732-18-5" }, { "name": "example uvcb reaction mass" }]
}
FieldTypeRequiredDescription
itemsobject[]yes1 to 200 items; each carries exactly one of cas / ec / name, same rules as the single call

The cap is checked before any lookup, so an empty list or one over 200 is rejected without being billed an answer. There is no at field — unlike /label/chem/classify/batch, the Candidate List is served as its last published version.

200 response — UnifiedResponse

{
  "data": {
    "count": 3,
    "results": [
      { "verdict": "listed", "entries": [ ... ] },
      {
        "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."
      },
      { "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" }
  }
}
  • count: number of results, equal to results.length and to the number of items submitted.
  • results: one verdict per item, in the same order as the input. Each well-formed element has exactly the same shape as the data of GET /label/chem/svhcverdict, entries[], and the dated note on not_listed.
  • A single provenance block covers the whole batch: every item is resolved against the same Candidate List snapshot.

See the single-call reference for the full field tables, the Article 57 reasons, the generic Article 33 + SCIP obligations and the dated not_listed note.

A malformed item stays at its position

A single bad item does not fail the batch: it is returned in place as { error, code }, so a caller can re-pair results without tracking indices.

{
  "data": {
    "count": 2,
    "results": [
      { "error": "invalid CAS check digit: '84-74-3'", "code": "INVALID_INPUT" },
      { "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" }
  }
}

Errors

Only requests the service cannot answer leave the 200 range. The cap is checked before any resolution, so an oversized batch is rejected without being billed.

StatuscodeCase
400INVALID_INPUTBody missing or not JSON, not an object, items missing, empty, or over 200
503DATA_UNAVAILABLEThe Candidate List dataset has not been ingested yet — there is no snapshot to serve
500INTERNALInternal error (detail logged, not exposed)
{ "error": "batch too large: 201 items, the maximum is 200", "code": "INVALID_INPUT" }

See also