Classify7

API

JSON in, JSON out. The full machine-readable contract is at /api/openapi.json and the ruleset itself at /api/reference.

The answer contract

Every classification returns exactly one of three determinations. Read this before you write the branch that consumes it, because the third one is the one that matters.

classified
An entry matched and every datum needed to pin down the packing group was present. isHazmat is true.
not_regulated
The material was positively identified as outside the regulations, and reasoning says why. isHazmat is false.
needs_review
Nothing matched, a required datum is missing, or two entries were too close to separate. isHazmat is null — unknown is not the same as false. Read missingData: it names the field to supply and exactly what that field would decide.

There is no fourth outcome where a plausible-looking guess is returned. If you are tempted to fall back to a default UN number on needs_review, don’t — a wrong UN number on a shipping paper is worse than no UN number.

Classify one product

Free and unauthenticated: 1 classification per rolling day per IP, with a burst limiter on top.

bash
curl -sX POST https://classify7.com/api/classify \
  -H 'Content-Type: application/json' \
  -d '{
    "sku": "CLN-IPA-500",
    "description": "70% isopropyl alcohol screen cleaner, 500 ml spray bottle",
    "composition": [{ "name": "Isopropyl alcohol", "cas": "67-63-0", "percent": 70 }],
    "modes": ["ground", "air"]
  }'
json
{
  "classificationId": "9f1c…",
  "determination": "classified",
  "isHazmat": true,
  "unNumber": "UN1219",
  "properShippingName": "Isopropanol",
  "hazardClass": "3",
  "hazardDivision": null,
  "packingGroup": "II",
  "labels": ["FLAMMABLE LIQUID"],
  "ergGuide": "129",
  "limitedQuantityEligible": true,
  "carrierEligibility": [
    { "carrier": "ups",   "ground": "contract-required", "air": "contract-required" },
    { "carrier": "usps",  "ground": "restricted",        "air": "prohibited" }
  ],
  "confidence": "medium",
  "evidence": [{ "kind": "cas", "matched": "67-63-0", "weight": 120 }],
  "missingData": [],
  "reasoning": ["…every step, in order…"],
  "ruleset": { "version": "2026.08.1", "hash": "f31c40d32828554081ce3e567617cd8c" }
}

When it needs more

Nail polish is UN1263, but the packing group turns on flash point. The API identifies the entry and refuses the packing group rather than picking one:

json
{
  "determination": "needs_review",
  "isHazmat": null,
  "unNumber": "UN1263",
  "properShippingName": "Paint or Paint related material",
  "packingGroup": null,
  "missingData": [{
    "field": "flashPointC",
    "decides": "The Class 3 packing group under 49 CFR 173.121. Without it the entry is
                identified but cannot be assigned a packing group — candidates are I, II, III."
  }]
}

Send flashPointC and it completes. The same pattern applies to concentrationPercent for Class 8, alcoholByVolumePercent for beverages, and lithium.wattHours for batteries.

Authentication

Create a key in the dashboard. It is shown once — only a SHA-256 hash is stored, so a lost key is replaced rather than recovered.

bash
curl -sX POST https://classify7.com/api/classify/batch \
  -H 'Authorization: Bearer classify7_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "batchRef": "catalogue-import-2026-08",
    "items": [
      { "sku": "A-1", "description": "Dry ice pellets, 5 kg" },
      { "sku": "A-2", "description": "20,000 mAh power bank",
        "lithium": { "chemistry": "ion", "packing": "alone", "unit": "battery", "wattHours": 74 } }
    ]
  }'

Up to 500 items per call. Each is classified independently, so one malformed row reports its own error in place rather than failing the batch.

Signed certificates

A certificate is issued against the stored determination, not against a fresh classification. Six months later, when the DGR edition has turned over, it still records what was returned at the time and under which ruleset hash — re-running the engine would quietly rewrite history, which is exactly what an audit trail exists to prevent.

bash
# PDF
curl -sX GET https://classify7.com/api/classify/{classificationId}/certificate \
  -H 'Authorization: Bearer classify7_live_…' -o certificate.pdf

# Signed JSON payload plus compact JWS
curl -sX GET 'https://classify7.com/api/classify/{classificationId}/certificate?format=json' \
  -H 'Authorization: Bearer classify7_live_…'

# The public key, so a recipient can verify without contacting us
curl -s https://classify7.com/api/certificate-key

Continuous re-classification

Put a SKU under monitoring, then call the recheck endpoint and every SKU whose stored ruleset hash has gone stale (a DGR edition turnover, a 49 CFR amendment, a carrier overlay correction) is re-run in that one call. Nothing re-checks itself on this deployment: point your own scheduler at the recheck endpoint for a hands-off sweep, which is cheap to run often since it skips anything still current. Supply a webhook and you are told what moved, field by field.

bash
curl -sX POST https://classify7.com/api/monitored-skus \
  -H 'Authorization: Bearer classify7_live_…' \
  -H 'Content-Type: application/json' \
  -d '{ "sku": "PB-20K",
        "input": { "description": "20,000 mAh power bank",
                   "lithium": { "chemistry": "ion", "packing": "alone",
                                "unit": "battery", "wattHours": 74 } },
        "webhookUrl": "https://example.com/hooks/classify7" }'

curl -sX POST https://classify7.com/api/monitored-skus/recheck \
  -H 'Authorization: Bearer classify7_live_…'

Errors

StatusMeaning
400Body was not valid JSON.
401An API key was presented but did not verify, or the endpoint needs one.
402Quota exhausted on the free tier, or a paid endpoint on a free plan.
403That classification belongs to another account.
404No stored classification with that id.
422Body failed schema validation. `issues` lists every field.
429Rate limited, or the free daily allowance is used up.
503A required capability is not configured on this deployment.

Ruleset 2026.08.1

f31c40d32828554081ce3e567617cd8c
  • 49 CFR as revised through the 1 October 2025 annual revision
  • IATA DGR 67th edition (effective 1 January 2026)
  • IMDG Code Amendment 42-24 (mandatory from 1 January 2026)

A curated subset of the 49 CFR 172.101 Hazardous Materials Table covering the substances and articles that appear in commercial parcel, e-commerce and FBA shipping, reconciled against the IATA DGR and the IMDG Code. Anything the table does not cover returns needs_review with the reason stated — it is never resolved to a plausible-looking guess.