SafetyDataSheetAPI is a product of Dscvry. Explore Dscvry →

Guide

UN Number Extraction from Safety Data Sheets

Where the transport data sits in an SDS, where it lands in the response, and how to check it before you rely on it.

Last updated: 2026-10-24

Where the transport data comes from

Section 14 of a safety data sheet holds the transport information: the UN number, the UN proper shipping name, the transport hazard class, the packing group and any special precautions. Documents often give values per transport mode: ADR for road, RID for rail, IMDG for sea and IATA for air.

Where it lands in the response

Everything from Section 14 is under the transport_information key of outputs.json. In our sample response it carries un_number, proper_shipping_name, hazard_class and packing_group. The contract guarantees the section key, not the field names inside it, and how a document's per-mode values appear depends on that document, so inspect a sample of your own SDS before you wire this to anything. See the coverage matrix.

An empty UN number can be correct

Many substances are not regulated for transport, and their SDS says so. For those the UN number, class and packing group are legitimately absent. Treat "no UN number" as a result to check against the document's Section 14 text, not automatically as an extraction failure.

Validate before you trust it

Extraction can be wrong, and transport data feeds safety-relevant decisions. These checks catch a lot cheaply: a UN number is the letters UN followed by four digits, the primary hazard class is 1 to 9 with divisions such as 2.1 or 4.3, the packing group is I, II or III, and a UN number comes with a proper shipping name. The script accepts 1090 and UN 1090 as well as UN1090:

import re

UN_NUMBER = re.compile(r"^UN\d{4}$")
HAZARD_CLASS = re.compile(r"^(1\.[1-6][A-Z]?|2\.[123]|3|4\.[123]|5\.[12]|6\.[12]|7|8|9)$")
PACKING_GROUPS = {"I", "II", "III"}


def check_transport(transport):
    """Return a list of problems with a transport_information object (empty list = looks consistent)."""
    problems = []
    un = re.sub(r"\s+", "", str(transport.get("un_number") or "")).upper()
    if un and not un.startswith("UN"):
        un = "UN" + un  # accept "1090" and "UN 1090"
    if un and not UN_NUMBER.match(un):
        problems.append(f"un_number {un!r} is not UN plus four digits")
    hazard_class = str(transport.get("hazard_class") or "").strip()
    if hazard_class and not HAZARD_CLASS.match(hazard_class):
        problems.append(f"hazard_class {hazard_class!r} is not a primary class 1-9")
    group = str(transport.get("packing_group") or "").strip().upper()
    if group and group not in PACKING_GROUPS:
        problems.append(f"packing_group {group!r} is not I, II or III")
    if un and not transport.get("proper_shipping_name"):
        problems.append("a UN number without a proper shipping name")
    if not un and (hazard_class or group):
        problems.append("a hazard class or packing group without a UN number")
    return problems

It checks the primary class only. A subsidiary risk written as 3 (6.1) will be flagged, so adjust the pattern to how your documents write it. Passing these checks means the values are well-formed, not that they are right: compare against the source for anything that matters.

A workflow for dangerous goods data

  1. Extract the document and read outputs.json.transport_information.
  2. Run the checks above. Anything with problems, warnings, or a low confidence score goes to a person.
  3. For documents that pass, compare the UN number and proper shipping name against your own dangerous goods reference data.
  4. Store the request_id with the record so a reviewer can trace it back to the source.

How you get access: the free sample upload on the homepage needs no key, the self-serve plan issues an API key after signup, and enterprise customers get a dedicated deployment with its own base URL. See pricing.

FAQ

Is the UN number always present?

No. Substances that are not regulated for transport have none, and their SDS says so. An empty value can be correct.

Does it return ADR, IMDG and IATA values separately?

That depends on how the document presents them. The contract guarantees the transport_information key, so inspect your own documents to see how per-mode values come back.

Can I rely on the extracted UN number for shipping?

Treat it as a draft. Validate the format, compare it with your dangerous goods reference data, and review anything flagged against the source document.

Related pages

Want to see it on your own document? Try a sample SDS or request an implementation plan.