POST /label/chem/classify/batch

Screens a list of substances in a single call: up to 200 entries, each by cas, ec or exact name, all evaluated at one date, settled with one x402 payment for the whole batch. Each item runs through the same engine as GET /label/chem/classify — the harmonised Annex VI (CLP) classification, versioned by ATP. 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 before shipping a label or a safety data sheet. 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_harmonised 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.

POST /label/chem/classify/batch
Content-Type: application/json
{
  "items": [{ "cas": "100-41-4" }, { "cas": "7732-18-5" }],
  "at": "2026-06-01"
}
FieldTypeRequiredDescription
itemsobject[]yes1 to 200 items; each carries exactly one of cas / ec / name, same rules as the single call
atstringnoYYYY-MM-DDcommon to the whole batch (a BOM is evaluated at one date); defaults to today

A single at for the whole batch is deliberate: a bill of materials is assessed against one regulatory version. The cap is checked before any lookup, so an empty list or one over 200 is rejected without being billed an answer.

200 response — UnifiedResponse

{
  "data": {
    "count": 2,
    "results": [
      { "verdict": "harmonised", "entries": [ ... ] },
      {
        "verdict": "not_harmonised",
        "entries": [],
        "note": "Absence does not mean the substance is not hazardous: self-classification under CLP remains the responsibility of the entity placing it on the market."
      }
    ]
  },
  "provenance": {
    "source": "ECHA Annex VI (CLP)",
    "fetched_at": "2026-07-24T07:45:50Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-05-01T00: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/classifyverdict, entries[], and note/upcoming where they apply.
  • A single provenance block covers the whole batch: every item is resolved against the same Annex VI snapshot, at the same at.

See the single-call reference for the full field tables, the ATP versioning (atp + upcoming) and the self-classification note carried by not_harmonised verdicts.

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: '100-41-5'", "code": "INVALID_INPUT" },
      { "verdict": "harmonised", "entries": [ ... ] }
    ]
  },
  "provenance": {
    "source": "ECHA Annex VI (CLP)",
    "fetched_at": "2026-07-24T07:45:50Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-05-01T00: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; a malformed at
503DATA_UNAVAILABLEThe Annex VI 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