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.
isHazmatistrue. - not_regulated
- The material was positively identified as outside the regulations, and
reasoningsays why.isHazmatisfalse. - needs_review
- Nothing matched, a required datum is missing, or two entries were too close to separate.
isHazmatisnull— unknown is not the same as false. ReadmissingData: 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.
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"]
}'{
"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:
{
"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.
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.
# 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-keyContinuous 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.
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
| Status | Meaning |
|---|---|
| 400 | Body was not valid JSON. |
| 401 | An API key was presented but did not verify, or the endpoint needs one. |
| 402 | Quota exhausted on the free tier, or a paid endpoint on a free plan. |
| 403 | That classification belongs to another account. |
| 404 | No stored classification with that id. |
| 422 | Body failed schema validation. `issues` lists every field. |
| 429 | Rate limited, or the free daily allowance is used up. |
| 503 | A 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.