How the work gets done

HTS Classifier

Classifies each line to a 10-digit HTS code, grounded on the live tariff schedule and CBP rulings.

6 min readUpdated Jul 17, 2026

Single-purpose LLM agent. Assigns a 10-digit HTSUS code to a product line, grounded on the live US tariff schedule and CBP's binding rulings.

What it does

Given a product description (and optionally COO, supplier, value, image), returns the 10-digit HTSUS code CBP expects on the entry summary, with a confidence score, a written rationale, alternative codes, and the CROSS rulings it cited as precedent. This is the most consequential decision on a customs entry — it drives the duty rate, whether Section 301/232/IEEPA apply, which PGA participates, and clearance.

Contract

Inputs. The customs-clearance module assembles the retrieval context before the model runs — the caller only supplies the product description (and any country of origin, supplier, value, quantity, or product photo it has). Into the prompt the module injects, per line:

  • ~40 pre-filtered candidate HTS codes — the most plausible headings for this description, retrieved from the live HTSUS by full-text and trigram search over a consumer→tariff synonym map (so "lamp" reaches "luminaire").
  • ~5 most relevant CBP rulings — the binding CROSS precedent that best matches.
  • Broker-corrected examples — prior overrides on this org's own entries, so the classifier defers to how this broker has already ruled similar goods.
  • Optional product image — passed to the model natively when a photo is available (multimodal).

Output (JSON, schema-validated):

{
  htsCode: "6109.10.0027",          // 10-digit, stat-suffix included
  confidence: 82,                    // 0–100, calibrated
  rationale: "Men's 100% cotton knit crew-neck t-shirts classify under …",
  alternatives: [{ hts_code, confidence, reason }],
  flags: ["UFLPA-sensitive chapter"]
}

When it runs

  • Per-line, every entry — in the draft → ai_review transition, unless the line already has a high-confidence stored classification.
  • In the Simulation pipeline — once per simulated PO line.
  • On-demand — broker clicks "Classify with Autopilot" on a corrected description.

Implementation

Model

Claude Sonnet, with extended thinking disabled and a low reasoning effort. Chosen by our own evaluation harness: it matched the larger Opus model on classification accuracy at roughly half the latency, and disabling adaptive thinking kept the model from spending its output budget mid-run and truncating the JSON rationale.

Prompt structure (layered, cached)

The system prompt is assembled from up to four blocks, cached at different lifetimes so the stable parts are reused across every line and only the per-product context is re-sent:

  1. System instructions (long-lived cache) — role, authority order, confidence calibration, output-format rules.
  2. Broker corrections / few-shot (long-lived cache) — recent (≤90d) overrides for this org.
  3. CROSS rulings (short-lived cache) — the top binding precedent for this line.
  4. HTSUS candidates (short-lived cache) — the pre-filtered candidate codes.

Authority order

How the model is instructed to weigh its sources: broker corrections → CROSS rulings → HTSUS candidates. A close broker-correction match wins and is cited as "per recent broker precedent"; otherwise a covering CROSS ruling is followed and cited by number; otherwise the model chooses among the candidate codes.

Confidence calibration

The prompt fixes what each confidence band means: 95+ only when a ruling directly covers the product or the description maps unambiguously to a single candidate; 80–94 when the heading is certain but the statistical suffix is ambiguous; below 80 when the heading itself is contested.

Two floors then act on that confidence — they are different guardrails:

  • 92% — auto-file floor (Autopilot): Autopilot only transmits at or above it; anything lower routes to the review band for a broker.
  • 70% — review-block floor (see Entry statuses, explained): a line below it hard-blocks the whole entry at the HTS Classification stage (red) until a broker reviews it — an unreviewed low-confidence code can never read as "ready to transmit."

Retrieval

Candidate retrieval (~40 codes) and ruling retrieval (~5 rulings) run in parallel: full-text search over the walked-up HTSUS descriptions plus the consumer→tariff synonym map for codes, and full-text search over ruling subject and body for precedent. If no candidate survives, the call errors rather than guess; if no ruling matches, the CROSS block is simply omitted and the model falls back to the HTSUS candidates alone.

Validation

The returned code is validated against the live HTSUS table; an unknown code is rejected and the call retries — so the classifier can never emit a code that doesn't exist in the tariff, giving a 0% hallucinated-code rate. If genuinely nothing fits, it returns 9999.99.99.99 at confidence 0 with an explanation in flags rather than force a wrong code.

Evaluation

Every prompt, model, or retrieval change is re-run through the eval harness before it ships. The methodology is built to be honest about real-world difficulty:

  • Each case is a real CBP ruling with a known-correct code.
  • The source ruling is excluded from retrieval, so the model can't simply copy the answer back — it has to reason from the surrounding tariff and other precedent.
  • The predicted code is scored at three levels of precision: heading (4-digit), subheading (6/8-digit), and exact 10-digit.
  • Every case runs twice — NO-RAG (the model reasoning alone) versus WITH-RAG (grounded on live HTSUS candidates and CBP rulings) — to isolate how much the grounding is actually worth.

The consistent finding: grounding the model on the live tariff and CBP rulings measurably improves accuracy over the model reasoning on its own, and the hallucinated-code rate is 0% — schema validation rejects any code that isn't in the live HTSUS, so a code that doesn't exist can never reach the broker.

Known failure modes. The hard cases are competing headings that turn on essential character — costume/festival apparel (6211 vs 9505), footwear (leather 6403 vs textile 6404), household vs. specialized articles (7323 vs 9617). These are GRI-3 essential-character calls, where two headings genuinely compete on which feature defines the good, and are the area we're actively improving.

Feedback loop

Every broker override becomes precedent the classifier defers to: the original and corrected codes, the reason, and the AI's offered alternatives are all recorded. Those corrections feed the org's few-shot block (so the model follows the broker's own prior rulings) and the eval gold set (so the change is measured against real broker decisions, not synthetic cases). The AI evaluation surface reports auto-approval rate, override rate by chapter, and confidence calibration.

What it does NOT do

  • Does not invent codes (schema validation rejects unknowns).
  • Does not decide whether to file — the broker approves and signs.
  • Does not handle specific (per-unit) duty rates — flagged for manual computation.
  • Does not classify multiple lines jointly — each line independent, errors contained.
  • Does not access the internet or chain calls — one prompt in, one object out.

Roadmap

GRI-native reasoning for essential-character calls, continued expansion of the CROSS ruling store, confidence recalibration, and semantic (vector) retrieval. This doc records what's shipped and live in production.

Ready to see it live?

Put your own book of entries through Commers.