Skip to content
decosa
LiveHostedSelf-host

Draft a tariff classification memo

A classification memo with a proposed HTS code, the rules applied and word-for-word quotes from similar CBP rulings, or a clear 'needs a broker'.

Held-out test145 / 200Right 6-digit subheading on the first pick (held-out test)
On production32 smedian on production (2026-09-27); slower when the service is busy
List price~$0.47 per 100 productsmeasured, at list price

Built on: Evidence retrieval, Signed record

Loading the tool…

Use it your way

Use it from your codeThe hosted API with your key, and prompts to paste into a coding agent
Hosted · by Decosa

Get an API key

  • Call the tariff classification memo API from your own code in minutes.
  • Every model answer carries a signed receipt.
  • Nothing to install; we run the models.
Self-host · your GPUs

Run it yourself, on request

  • The same open models and app, on 1x RTX PRO 6000 (96 GB) for Qwen3.8-27B; the embedder and reranker fit in about 10 GB beside it or on a second card; the checks and signing run on CPU.
  • Data never leaves your machines, and there are no Decosa charges.
  • One prompt for Claude Code or Codex assembles the whole stack.
  • Early access: the container images are not public yet and the source needs access; the prompt says how to ask.

Build with it

Paste one of these into Claude Code, Codex or another coding agent. The first wires your project to the hosted API with your DECOSA_API_KEY. The second pulls our containers and runs the same stack on your own GPU, with no Decosa charges.

Base URL
https://api.decosa.ai
Auth
Authorization: Bearer $DECOSA_API_KEY (or a demo session token)
Tool id
tariff-classification

Use the hosted API

# Decosa tariff classification memo: use the hosted API

You are wiring Decosa's tariff classification memo into this project (a product-onboarding flow, a classification
worksheet or a broker's tool). Given a product description it returns CBP rulings on similar products with the passages
that matched, a proposed HTS heading, subheading and statistical number with the GRI applied and word-for-word quotes
(each checked in code), or "needs a broker" with the reason, plus a memo and a signed record. Every model call has a
signed receipt. Use only what is listed below; if you need something else, stop and ask me.

- Base URL: `https://api.decosa.ai`
- Health check: `GET https://api.decosa.ai/healthz`.
- It searches a subset of public CBP New York rulings (since 2018, 35 headings in 11 chapters) and one HTS release; see
  `GET /tariff/info` for the set, its hash and the release.
- This is a research and drafting aid. A licensed customs broker or the importer of record decides the classification,
  and only CBP issues binding rulings (19 CFR Part 177). Never file an entry on the memo alone.
- Do not send confidential product specifications to the hosted demo; self-host for those.

## Auth: API key (or a demo session)
1. Preferred: an API key (`dk_…`) from "Get an API key" on the tool page. Keep it in `DECOSA_API_KEY`, never in code.
   Send `Authorization: Bearer $DECOSA_API_KEY`.
2. Without a key: `POST https://api.decosa.ai/demo/session` with `{"vertical": "tariff-classification"}` returns
   `{"token", "expires_at", "budget"}`. A demo token runs one memo at a time (409 otherwise); over a limit you get 429
   with `Retry-After`.
3. A memo needs about 1,400 generated tokens left in the budget before it starts (402 otherwise).

## Classify
- `POST /tariff/classify` (token). Body: `{"description": "30-4,000 characters: what it is, materials and their shares, how it is made, what it is for", "facts"?: "up to 2,000 characters", "title"?, "stream"?}` or `{"sample_id": "wall-heater"}`.
- The JSON answer has `status` (`proposed` or `needs_broker`), `reasons`, `proposal` (`heading`, `subheading`, `hts`,
  `display`, `text`, `general_rate`, `alternatives`, `gri`, `reasoning`, `missing`, `confidence`, `ruling_citations`
  with `ok` and byte offsets, `hts_citations`), `checks`, `rulings` (each with `excerpts` and byte offsets),
  `search_receipts` (signed, naming `index_hash`), `memo_md`, `record`, `receipts` and `note`.
- With `Accept: text/event-stream` (or `"stream": true`) the events are `ready`, `rulings`, a `receipt` per model call,
  `proposal`, `result`, `budget` and `done`.
- `POST /record/verify` (no token) `{"record": {...}}` re-checks the memo record. `GET /tariff/info`, `GET /tariff/samples`
  and `GET /attest/signing-key` need no token.

## Example (Python, `pip install httpx`)
```python
import httpx, os
API = "https://api.decosa.ai"
H = {"Authorization": f"Bearer {os.environ['DECOSA_API_KEY']}"}
desc = ("Soft-sided insulated lunch bag; outer surface 600-denier polyester woven fabric; PEVA plastic liner over foam; "
        "zipper, carry handle, shoulder strap; keeps a packed lunch cold.")
r = httpx.post(f"{API}/tariff/classify", json={"description": desc, "title": "Lunch bag SKU 1182"}, headers=H, timeout=180)
r.raise_for_status()
js = r.json()
print(js["status"], (js["proposal"] or {}).get("display"), js["reasons"])
for c in (js["proposal"] or {}).get("ruling_citations", []):
    print(c["ruling"], "verified" if c["ok"] else "NOT VERIFIED", c["quote"])
open("memo.md", "w").write(js["memo_md"])            # for the broker
```
When `status` is `needs_broker`, show the reasons to a person and do not use the code.

Run it yourself (containers)

On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.

# Decosa tariff classification memo: run it yourself (containers)

You are setting up Decosa's tariff classification memo on this machine, so product descriptions never leave it. It
searches public CBP rulings and the HTS text with an open embedder and reranker, one open model (Qwen3.8-27B) proposes a
heading, subheading and statistical number with quotes, and code checks every code and quote. The output is a memo and a
signed record. Nothing is sent to Decosa's hosted API; the only outbound calls are the one-time downloads of the public
ruling set and the HTS.

Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.

Ask me before any command that needs sudo, and show me the command first.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/tariff-classification.zip (2 KB, 9 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py tariff-classification` (the api image carries the same bundle under /app/rehearsal/tariff-classification/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py tariff-classification --bundle tariff-classification.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the heater is proposed", "under heading 8516", "subheading 8516.29"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin using Docker's official
   instructions for this distribution. Install the NVIDIA container toolkit, then check
   `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`. I need one GPU with 64 GB or more
   (or two GPUs).
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`.
   Keep the `llm` and `api` services and add a `retrieval` service built from `services/retrieval` in the decosa-api
   source (Qwen3-Embedding-0.6B and Qwen3-Reranker-4B, Apache-2.0; the full recipe is the tool's assemble prompt).
   On the `api` service set `DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`,
   `DECOSA_RETRIEVAL_URL=http://retrieval:8499`, `DECOSA_TARIFF_DATA=/data/tariff` and `DECOSA_TARIFF_WARM=1`; keep its
   data on a named volume and bind every port to 127.0.0.1.
3. `docker compose up -d --build`; wait for the health checks.
4. Fetch the ruling set and the HTS once: `docker compose exec api python scripts/tariff_fetch.py --out /data/tariff`,
   then `docker compose restart api`. `GET /tariff/info` should show about 2,000 rulings and `retrieval.reachable: true`.
5. Smoke test: get a token with `POST /demo/session {"vertical":"tariff-classification"}`, then
   `POST /tariff/classify {"sample_id":"wall-heater"}`. Expect heading 8516 with at least one verified ruling quote, every
   receipt `attested`, and `POST /record/verify {"record": <record>}` giving `ok: true`. `{"sample_id":"vague-bag"}`
   should come back `needs_broker`.
6. Report back: the public key (`GET /attest/signing-key`), the ruling-set count and index hash, and the smoke results.

A research and drafting aid: a licensed customs broker or the importer of record decides, and only CBP issues binding
rulings.

Off by default. Don't join as a provider on a box that holds unreleased product specifications.
Run it on your own hardwareWhat it needs, and the prompt that sets it up

Run it on your own GPU

Same app, same pinned models, your hardware. Nothing goes to our servers and there are no Decosa charges.

  • CPU only, 64 GB RAMDoesn't fit

    Qwen3.8-27B (NVIDIA NVFP4) needs a GPU.

  • GeForce RTX 4090Doesn't fit

    Needs about 33 GB of GPU memory at the smallest settings; 24 GB available.

  • GeForce RTX 5090Doesn't fit

    Needs about 41 GB of GPU memory at the smallest settings; 32 GB available.

  • 2x GeForce RTX 5090standard tierRuns

    The standard tier fits with changes: Qwen3.8-27B (NVIDIA NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.

  • L40Sstandard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.

  • H100 80 GB (SXM)standard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.

  • RTX PRO 6000 Blackwell 96 GBstandard tierRuns

    The standard tier fits (70.6 of 96 GB).

  • 2x RTX PRO 6000 Blackwell 96 GBstandard tierRuns

    The standard tier fits (70.6 of 192 GB).

  • Apple M3 Ultra (Mac Studio), 96 GBCan't tell

    Memory not known for Qwen3-Embedding-0.6B has no mapped Apple Silicon build; Qwen3-Reranker-4B has no mapped Apple Silicon build.

  • Apple M5 Max, 64 GBCan't tell

    Memory not known for Qwen3-Embedding-0.6B has no mapped Apple Silicon build; Qwen3-Reranker-4B has no mapped Apple Silicon build.

Memory per component comes from measured footprints, the tool's stack.json, or an estimate from its parameter count, and each is labelled that way below. Only an RTX PRO 6000 and an M3 Ultra Mac Studio have actually been run.

On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.

  1. 1

    Check the GPU, Docker and the NVIDIA Container Toolkit

    The driver must see the GPU, and Docker must be able to pass it into a container.

    nvidia-smi
    docker compose version
    docker run --rm --gpus all ubuntu nvidia-smi
  2. 2

    Fetch the compose file

    One file describes the API and the language model as services.

    mkdir -p ~/decosa && cd ~/decosa
    curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml
  3. 3

    Pull and start

    The first start downloads pinned model weights, tens of gigabytes.

    docker compose pull
    docker compose up -d
  4. 4

    Check health

    Wait until the API reports ok with the language model loaded. Then point your app at the local base URL.

    curl -fsS http://localhost:<PORT>/healthz
    # {"ok": true, "llm": true, ...}
    curl -fsS -X POST http://localhost:<PORT>/demo/session \
      -H 'Content-Type: application/json' -d '{"vertical":"tariff-classification"}'

Set up with a coding agent, rehearse on mock data, then go private

  1. Set up with a coding agent. Paste the self-host prompt into a coding agent on the machine that will run the service. We recommend Claude Code with Claude Opus 5.5; any capable coding agent works.
  2. Rehearse on mock data. The agent runs the tool on a bundle of synthetic inputs and checks each answer against the bundle's expected.json. Every check must print PASS.
  3. Go private. Only then do you run your own data against the local API, yourself, on that machine. Never give the agent real data during setup: a coding agent that runs in the cloud sees everything in its context, so keep real data out of the chat and out of the files it reads.
Rehearsal command
docker compose exec api python scripts/rehearse.py tariff-classification

Download the mock-data bundle (2 KB, 9 checks)expected.json

Two synthetic product descriptions. The wall-mounted, hard-wired fan heater must be proposed under heading 8516 (subheading 8516.29) with at least one quote verified word for word in a retrieved CBP ruling; the bag described only as a bag with a strap must go to a broker. The memo record must verify. Run it against your own ruling set: the ruling numbers you see depend on the set fetched.

What the rehearsal checks
  • the heater is proposed
  • under heading 8516
  • subheading 8516.29
  • at least one ruling quote verified word for word
  • a retrieved ruling was classified by CBP in 8516
  • the ruling search has a signed search receipt
  • the memo record verifies
  • the thin description goes to a broker
  • every model call has a signed receipt

Licence: Synthetic descriptions written for decosa-api (AGPL-3.0-or-later). The rulings searched are CBP CROSS rulings and the HTS text, US Government works in the public domain.

Prompt for your coding agent

# Decosa tariff classification memo: run it yourself (containers)

You are setting up Decosa's tariff classification memo on this machine, so product descriptions never leave it. It
searches public CBP rulings and the HTS text with an open embedder and reranker, one open model (Qwen3.8-27B) proposes a
heading, subheading and statistical number with quotes, and code checks every code and quote. The output is a memo and a
signed record. Nothing is sent to Decosa's hosted API; the only outbound calls are the one-time downloads of the public
ruling set and the HTS.

Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.

Ask me before any command that needs sudo, and show me the command first.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/tariff-classification.zip (2 KB, 9 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py tariff-classification` (the api image carries the same bundle under /app/rehearsal/tariff-classification/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py tariff-classification --bundle tariff-classification.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the heater is proposed", "under heading 8516", "subheading 8516.29"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin using Docker's official
   instructions for this distribution. Install the NVIDIA container toolkit, then check
   `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`. I need one GPU with 64 GB or more
   (or two GPUs).
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`.
   Keep the `llm` and `api` services and add a `retrieval` service built from `services/retrieval` in the decosa-api
   source (Qwen3-Embedding-0.6B and Qwen3-Reranker-4B, Apache-2.0; the full recipe is the tool's assemble prompt).
   On the `api` service set `DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`,
   `DECOSA_RETRIEVAL_URL=http://retrieval:8499`, `DECOSA_TARIFF_DATA=/data/tariff` and `DECOSA_TARIFF_WARM=1`; keep its
   data on a named volume and bind every port to 127.0.0.1.
3. `docker compose up -d --build`; wait for the health checks.
4. Fetch the ruling set and the HTS once: `docker compose exec api python scripts/tariff_fetch.py --out /data/tariff`,
   then `docker compose restart api`. `GET /tariff/info` should show about 2,000 rulings and `retrieval.reachable: true`.
5. Smoke test: get a token with `POST /demo/session {"vertical":"tariff-classification"}`, then
   `POST /tariff/classify {"sample_id":"wall-heater"}`. Expect heading 8516 with at least one verified ruling quote, every
   receipt `attested`, and `POST /record/verify {"record": <record>}` giving `ok: true`. `{"sample_id":"vague-bag"}`
   should come back `needs_broker`.
6. Report back: the public key (`GET /attest/signing-key`), the ruling-set count and index hash, and the smoke results.

A research and drafting aid: a licensed customs broker or the importer of record decides, and only CBP issues binding
rulings.

Off by default. Don't join as a provider on a box that holds unreleased product specifications.

Help me customise for my hardware

Pick your GPU or Mac, or enter its memory. You get the tier that fits, the model swaps it needs, measured speed where we have it, and a setup prompt with those choices written in.

Hardware

GeForce RTX 5090: 32 GB GDDR7, 1,792 GB/s, FP8 and NVFP4. NVIDIA product page

Doesn't fitTariff classification memo on GeForce RTX 5090

Needs about 41 GB of GPU memory at the smallest settings; 32 GB available.

Lite · smaller reranker: what changesuses estimates

  • Needs about 32.8 GB of GPU memory at the smallest settings; 32 GB available.
Memory per component
  • One call per memo: Qwen3.8-27B (NVIDIA NVFP4). ~57.6 GB (at least ~28 GB), weights 21.4 GB (from stack.json). Qwen3.8-27B NVFP4: Weights 19.9 GiB (21.4 GB), measured (field stack.json). The compose file gives the server 0.60 of a 96 GB card (57.6 GB) so the rest is FP8 KV cache for several sessions. The 28 GB minimum is an estimate: weights plus a short-context KV cache, which is why several stacks list a 32 GB RTX 5090 as 'estimate'. (stack.json lists 57 GB for this component.)
  • Embeds the ruling set once: Qwen3-Embedding-0.6B. ~2.4 GB, weights 1.2 GB (estimate). Estimate: 0.6B parameters at 2 bytes (BF16) per weight is about 1.2 GB, plus 20% working memory and 1 GB of runtime. Not measured.
  • The smaller reranker for a card with less mem...: Qwen3-Reranker-0.6B. ~2.4 GB, weights 1.2 GB (estimate). Estimate: 0.6B parameters at 2 bytes (BF16) per weight is about 1.2 GB, plus 20% working memory and 1 GB of runtime. Not measured.
  • Codes against the HTS release, codes nest, qu...: Checks and record (decosa-api, Python). CPU. Runs on CPU (vram_gb 0 in stack.json).

Expected speed

Not measured.

Not measured on this hardware. The only measured setups are an RTX PRO 6000 Blackwell and a Mac Studio M3 Ultra.

Setup prompt for this hardware

The self-host prompt for Tariff classification memo, with a hardware plan for GeForce RTX 5090 added after Step 0. Loading the full prompt; until then it points your agent at the prompt's URL.

# Set up Tariff classification memo on my hardware

Fetch https://decosa.ai/prompts/tariff-classification-selfhost.md and follow it (including Step 0: rehearse on mock data first), with the hardware plan below applied.

## Hardware plan for this machine (from https://decosa.ai/self-host/hardware?use=tariff-classification)

Target machine: GeForce RTX 5090 (32 GB of GPU memory; CUDA, FP8 and NVFP4).
Quality tier: Lite · smaller reranker (lite). Fit check: doesn't fit; some memory numbers are estimates, not measurements.

First, check the machine: run `nvidia-smi` (or `rocm-smi`, or `sysctl hw.memsize` on a Mac) and confirm the GPUs and free memory match the line above. If they do not, stop and tell me before pulling anything.

Use these components (the setup below describes the standard tier; change it to match):
- One call per memo: Qwen3.8-27B (NVIDIA NVFP4) (nvidia/Qwen3.8-27B-NVFP4), 57.6 GB
- Embeds the ruling set once: Qwen3-Embedding-0.6B (Qwen/Qwen3-Embedding-0.6B), 2.4 GB
- The smaller reranker for a card with less mem...: Qwen3-Reranker-0.6B (Qwen/Qwen3-Reranker-0.6B), 2.4 GB
- Codes against the HTS release, codes nest, qu...: Checks and record (decosa-api, Python), CPU

Warning: the fit check says this tier does not fit: Needs about 32.8 GB of GPU memory at the smallest settings; 32 GB available. Tell me before going further.

During the rehearsal, watch GPU memory. If a model fails to load or runs out of memory, lower its --max-model-len and --max-num-seqs first, then its memory share, and tell me what you changed.

The stack's own component list and compose layout: https://decosa.ai/prompts/tariff-classification-assemble.md

The proof

How we tested itEval results and end-to-end checks, hosted and self-hosted, with dates

Verified end to end

Hosted: verified 27 Sep 2026 · measured 27 Sep 2026: · p50 32 s · ~$0.004 per run · 1 receipt

Loading the nightly status…

Self-host: verified 27 Sep 2026 · fresh clone into a clean directory, the api image built from it, compose up (named volume), rehearsal bundle and the heater sample against the already-running local Qwen3.8-27B (direct route) and decosa-retrieval services

Measured cost to run: about $0.47 per 100 products (hosted, 27 Sep 2026, partly estimated). Self-hosting is free: the code is open and the models are open-weight. You pay only for your own hardware and power.

9/9 rehearsal checks with the bundled 198-ruling sample set; receipts attested. The retrieval container build and the one-hour ruling fetch in the assemble prompt were not re-run in the sandbox (no new GPU loads; the fetch ran on the host).

Known limits (4)
  • Hosted verification ran on the pre-release server (decosa-api the pre-release branch on our server, gateway route). The production API gets this tool when the branch merges.
  • The eval asks about products CBP already ruled on, described in CBP's own words; real product sheets are vaguer, so expect more 'needs a broker'.
  • Older rulings cite statistical numbers that no longer exist; the memo keeps the valid 8- or 6-digit prefix and flags it.
  • 1,992 rulings in 35 headings; a product outside those chapters gets thin evidence and goes to a broker.

Eval results, nightly checks and cost per runVerify a run

How it's builtThe steps, the models and what each one checks
Hosted · by Decosa

Get an API key

  • Call the tariff classification memo API from your own code in minutes.
  • Every model answer carries a signed receipt.
  • Nothing to install; we run the models.
Self-host · your GPUs

Run it yourself, on request

  • The same open models and app, on 1x RTX PRO 6000 (96 GB) for Qwen3.8-27B; the embedder and reranker fit in about 10 GB beside it or on a second card; the checks and signing run on CPU.
  • Data never leaves your machines, and there are no Decosa charges.
  • One prompt for Claude Code or Codex assembles the whole stack.
  • Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
The open stack

A product description in; CBP rulings on similar products, a proposed HTS code with the GRI and word-for-word quotes, or a clear 'needs a broker', out.

For customs brokers and importers' trade-compliance teams. It searches public CBP New York rulings and the HTS text of the candidate headings, an open model proposes a heading, subheading and statistical number with the General Rules of Interpretation it applied, and code checks every code against the HTS release and every quote against the ruling text it retrieved. When a fact the code turns on is missing, or the evidence is thin, it says so. A research aid: a licensed broker decides.

Deployment
Hosted or self-host
Regulatory
Checked 27 Sep 2026. Importers must use reasonable care to classify (19 U.S.C. 1484, https://www.law.cornell.edu/uscode/text/19/1484); binding rulings come only from CBP under 19 CFR Part 177 (https://www.ecfr.gov/current/title-19/chapter-I/part-177), and a ruling binds only the transaction it describes. Customs business, including classification for others, is for licensed customs brokers (19 U.S.C. 1641, https://www.law.cornell.edu/uscode/text/19/1641). The General Rules of Interpretation are quoted from the HTS (USITC, 2026 Revision 19, https://hts.usitc.gov). The memo is not a ruling, not advice, and not a filing; Chapter 99 duties (Section 301, 232, IEEPA) are not applied.
Architecture
Text description

A product description goes to decosa-api. The evidence retrieval block searches a hashed snapshot of public CBP rulings and the HTS heading texts with Qwen3-Embedding-0.6B and Qwen3-Reranker-4B (Apache-2.0), returning chunks with byte offsets and a signed search receipt. Qwen3.8-27B (Apache-2.0) reads the rulings, the heading texts and the GRI and proposes a code with quotes. Code checks every code against the HTS release and every quote word for word, and anything unsupported goes to a broker. Outputs: a memo for a licensed broker and a signed, hash-chained record. Hosted, model calls get gateway receipts; self-hosted, everything stays on your machine.

Architecture

At a glance

What it gives you
A memo: the rulings found (with the matching passages), a proposed heading, subheading and statistical number with the GRI applied and verified quotes, up to two alternatives, the facts a broker should confirm, and a signed record naming the ruling set and the index searched.
Coverage
It covers CBP New York rulings in 35 headings of 11 chapters. Chapters: 39, 42, 61, 62, 63, 64, 73, 84, 85, 94, 95. Headings: 3923, 3924, 3926, 4202, 6104, 6109, 6110, 6114, 6204, 6211, 6307, 6402, 6403, 6404, 7323, 7326, 8414, 8419, 8471, 8479, 8481, 8504, 8516, 8517, 8518, 8528, 8543, 8544, 9401, 9403, 9405, 9503, 9504, 9505, 9506. A product outside them comes back as "out of coverage", naming the heading it would need; a licensed customs broker can classify it. Inside them, a description that lacks a deciding fact comes back as "needs a broker".
What it does not do
It does not decide, file or rule: a licensed customs broker or the importer of record decides, and only CBP issues binding rulings. It searches 1,992 New York rulings in 35 headings, not all of CROSS, and not HQ rulings, court cases or the Explanatory Notes. It does not apply Chapter 99 duties or decide origin or value.
Data retention
Nothing stored: the description lives in memory for the request. Logs carry counts and timings only.
Evidence it keeps
The record commits to the description's hash, the ruling-set hash, the HTS release, the retrieval index hash (a Merkle root over every chunk), the search and model receipts, the proposal and every check.
Quality tiers

Pick the tier for the quality you need

Same app at every tier. What changes is the models, the hardware they need, and whether receipts are signed. Scores are measured with the source named, or marked not measured.

  • Lite

    smaller reranker

    Qwen3-Reranker-0.6B instead of the 4B: about 7 GB less GPU memory. Not measured on this tool.

    Models
    • Qwen3.8-27B (NVIDIA NVFP4)
    • Qwen3-Embedding-0.6B
    • Qwen3-Reranker-0.6B
    • Checks and record (decosa-api, Python)
    Hardware
    1x 80-96 GB card
    Quality evidence
    • held-out accuracy with the 0.6B rerankernot measured yet
    Latency
    not measured
    Verification
    Proof: strongSelf-host onlySame receipts as standard.
  • In the hosted demo

    Standard

    the hosted demo

    Hybrid search with the 4B reranker, one Qwen3.8-27B call, the checks and the signed record.

    Models
    • Qwen3.8-27B (NVIDIA NVFP4)
    • Qwen3-Embedding-0.6B
    • Qwen3-Reranker-4B
    • Checks and record (decosa-api, Python)
    Hardware
    1x RTX PRO 6000 Blackwell 96 GB (or the LLM and retrieval on two cards)
    Quality evidence
    • memo top-1 subheading (6-digit), held-out rulings (n=200, run once)145 / 200decosa-api docs/evals/tariff-classification.md, 2026-09-27
    • memo top-1 heading (4-digit), held-out171 / 200decosa-api docs/evals/tariff-classification.md, 2026-09-27
    • memo top-3 subheading, held-out172 / 200decosa-api docs/evals/tariff-classification.md, 2026-09-27
    • memos proposed (not sent to a broker) and their subheading accuracy, held-out108 / 200 proposed; 81% rightdecosa-api docs/evals/tariff-classification.md, 2026-09-27
    • same model without retrieval, top-1 subheading, held-out57 / 200decosa-api docs/evals/tariff-classification.md, 2026-09-27
    • ruling vote alone (hybrid + rerank) / BM25 alone, top-1 subheading, held-out118 / 200 / 116 / 200decosa-api docs/evals/tariff-classification.md, 2026-09-27
    Latency
    measured on our server: about half a minute per memo on shared GPUs
    Verification
    Proof: strongGateway-signed receipt for the model call; model-call receipts signed by decosa-api for the embedder and reranker; a signed search receipt with the index hash.
Components

Every model in the stack

Models in this stack. Each row has a button that shows its licence, engine, verification and evidence.
ModelDetails
One call per memo: reads the rulings found, the HTS text of the candidate headings and the GRI, and proposes a heading, subheading and statistical number with quotes, or declines.Qwen3.8-27B (NVIDIA NVFP4)nvidia/Qwen3.8-27B-NVFP4 on Hugging Face (opens in a new tab)
27.8B · 57 GBProof: strongIn the hosted demo
Embeds the ruling set once (cached) and each product description, for the dense half of the hybrid search (BM25 is the other half).Qwen3-Embedding-0.6BQwen/Qwen3-Embedding-0.6B on Hugging Face (opens in a new tab)
0.6B · about 1.2 GB (estimate)Proof: partialIn the hosted demo
Scores the 40 best chunks for each description, so the rulings the model reads are the closest products, not the closest words.Qwen3-Reranker-4BQwen/Qwen3-Reranker-4B on Hugging Face (opens in a new tab)
4B · about 8.1 GB (estimate)Proof: partialIn the hosted demo
The smaller reranker for a card with less memory.Qwen3-Reranker-0.6BQwen/Qwen3-Reranker-0.6B on Hugging Face (opens in a new tab)
0.6B · about 1.2 GB (estimate)Proof: partialSelf-host only
Codes against the HTS release, codes nest, quotes word for word with byte offsets, a cited ruling at the proposed heading, rulings agree, evidence not thin; the signed record.Checks and record (decosa-api, Python)
0 GBNo proof yetIn the hosted demo

Measured

How often is the proposed code right?

Held-out CBP New York rulings: the product description from each ruling (tariff numbers masked, the classification paragraphs cut) against the code CBP gave. The index never holds the held-out rulings or near duplicates of them. Prompts and checks were set on 60 dev rulings; the 200 test rulings were run once.

Memo, top-1 heading / subheading
86% / 72%n = 200; top-3 subheading 86%
Proposed memos only
81% subheading right108 of 200 proposed; the rest sent to a broker with the reason
Same model, no retrieval
50% / 28%heading / subheading, top-1
Rulings' vote, no model (hybrid + rerank / BM25)
59% / 58%subheading, top-1

Retrieval is most of the gain

The same model with no rulings to read gets the subheading right far less often; with them it beats the rulings' own vote because it reads the product against the heading texts.

It declines a lot

Many memos go to a broker, usually because the description lacks a fact the line turns on (fibre shares, gender, essential character) or a quote could not be verified. That is the intended direction of error, and it costs coverage.

Source: decosa-api docs/evals/tariff-classification.md, 2026-09-27; docs/evals/tariff-classification/test-score.json

Around the models

Tools, services and hardware

Tools

Services

  • decosa-api:8445
    ${DECOSA_REGISTRY}/decosa-api:0.1.0

    The ruling set and HTS, the checks, the record and the HTTP API (/tariff/*). No GPU.

  • decosa-retrieval:8499

    The evidence retrieval block's embedder and reranker (services/retrieval). Internal only.

  • decosa-llm:8000
    ${DECOSA_REGISTRY}/decosa-llm:0.1.0

    vLLM OpenAI endpoint for Qwen3.8-27B. Internal only.

Hardware

  • 1x RTX PRO 6000 Blackwell 96 GB Fits

    Measured on our server: the LLM on one card, the embedder and reranker (12.4 GB peak) on the other; both fit one card by the numbers (57 + 12.4 GB), not measured together.

  • Two 48 GB cards

    Not measured: FP8 LLM on one, retrieval on the other.

Latency per lane

  • one memo, hosted (search + one model call), busy shared GPUs32.1 s

    Measuredmeasured on our server 2026-09-27, pre-release server, gateway route

  • index the 1,992 rulings (about 10,000 chunks), once per ruling-set version141.8 s

    Measuredmeasured on our server 2026-09-27; cached on disk afterwards

Assemble it

Run this exact stack on your machine

Paste into Claude Code / Codex to assemble this stack locally. The prompt checks your GPU, pulls the pinned models, writes the compose file and runs a smoke test.

tariff-classification/assemble-prompt.md184 lines
# Assemble the Decosa tariff classification memo on this machine

You are setting up a self-hosted tariff classification memo on this Linux machine for a customs broker or an importer's
trade-compliance team. Given a product description, it searches public CBP rulings (CROSS, New York classification
rulings) and the HTS text, and returns:
- a proposed HTS heading, subheading and statistical number with the General Rules of Interpretation applied, up to two
  alternatives, and quotes from the rulings and the HTS, each quote checked word for word in code; or "needs a broker"
  with the reason (a missing fact, thin evidence, rulings that disagree, a quote that could not be verified);
- a memo in Markdown and a signed, hash-chained record naming the ruling-set version, the HTS release and the retrieval
  index hash.

Work step by step, show me each command before running anything that needs sudo, and stop if a check fails.

**Before anything else, remind me:**
- Product descriptions stay on this machine. The only outbound calls are the one-time downloads of the public ruling set
  (rulings.cbp.gov) and the HTS (hts.usitc.gov); after that it can run offline.
- This is a research and drafting aid. A licensed customs broker or the importer of record decides the classification,
  and only CBP issues binding rulings (19 CFR Part 177).

Repeat both points in your final summary.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/tariff-classification.zip (2 KB, 9 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py tariff-classification` (the api image carries the same bundle under /app/rehearsal/tariff-classification/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py tariff-classification --bundle tariff-classification.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the heater is proposed", "under heading 8516", "subheading 8516.29"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## What you are building

| service | image | model | port |
|---|---|---|---|
| `llm` | `${DECOSA_REGISTRY}/decosa-llm:0.1.0` (vLLM 0.29.0) | `nvidia/Qwen3.8-27B-NVFP4` @ `482ca0f3832238542f8f5295dde86b5f22711d80`, Apache-2.0 | internal 8000 |
| `retrieval` | built here from `services/retrieval` (PyTorch, CUDA) | `Qwen/Qwen3-Embedding-0.6B` + `Qwen/Qwen3-Reranker-4B`, Apache-2.0 (revisions pinned in `services/retrieval/models.py`) | internal 8499 |
| `api` | `${DECOSA_REGISTRY}/decosa-api:0.1.0` (no GPU) | none | `127.0.0.1:8445` |

## 1. Check the GPU, driver and Docker

1. Run `nvidia-smi`. I need one NVIDIA GPU with at least 64 GB (the LLM plus about 10 GB for the embedder and reranker),
   or two GPUs (LLM on one, retrieval on the other), driver 580 or newer. Blackwell uses the NVFP4 build (measured);
   Hopper: set `LLM_MODEL=Qwen/Qwen3.8-27B-FP8` and `LLM_REVISION=main` (not measured).
2. Check `docker compose version` and `docker run --rm --gpus all ubuntu nvidia-smi`. If Docker or the NVIDIA Container
   Toolkit is missing, install them from the official repositories, run `sudo nvidia-ctk runtime configure --runtime=docker`
   and restart Docker.
3. Confirm about 60 GB of free disk.

## 2. Get the source and the images

The images are **on request** while self-host is in early access: ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry (set it as `DECOSA_REGISTRY`), pull access and the compose file. Try `docker pull ${DECOSA_REGISTRY}/decosa-{llm,api}:0.1.0`; if that
fails, build from the decosa-api source (`docker build -f docker/api/Dockerfile -t ${DECOSA_REGISTRY}/decosa-api:0.1.0 .`).
Clone decosa-api either way: the retrieval service is built from it. If neither works, stop and tell me.

Create `services/retrieval/Dockerfile` in the clone if it is not there:

```dockerfile
FROM pytorch/pytorch:2.9.0-cuda12.8-cudnn9-runtime
WORKDIR /app
COPY services/retrieval/ /app/
RUN pip install --no-cache-dir "transformers>=4.57,<6" "sentence-transformers>=5.1" "fastapi>=0.115" "uvicorn>=0.30" numpy
ENV RETRIEVAL_HOST=0.0.0.0 RETRIEVAL_PORT=8499 HF_HOME=/models
CMD ["python", "server.py"]
```

## 3. Write the compose file

`~/decosa-tariff/.env`:

```bash
DECOSA_TAG=0.1.0
LLM_MODEL=nvidia/Qwen3.8-27B-NVFP4
LLM_REVISION=482ca0f3832238542f8f5295dde86b5f22711d80
LLM_GPU_UTIL=0.70
DECOSA_SIGNER_NAME="<who signs the memo records, e.g. Example Customs Brokerage>"
```

`~/decosa-tariff/docker-compose.yml` (set `context` to the clone's path):

```yaml
name: decosa-tariff
x-health: &health { interval: 15s, timeout: 5s, retries: 5 }
services:
  llm:
    image: ${DECOSA_REGISTRY}/decosa-llm:${DECOSA_TAG}
    deploy: { resources: { reservations: { devices: [ { driver: nvidia, device_ids: ["0"], capabilities: [gpu] } ] } } }
    ipc: host
    restart: unless-stopped
    volumes: [hf-cache:/root/.cache/huggingface]
    command: ["${LLM_MODEL}", "--revision", "${LLM_REVISION}", "--served-model-name", "qwen3.8-27b", "--language-model-only",
              "--max-model-len", "65536", "--gpu-memory-utilization", "${LLM_GPU_UTIL}", "--kv-cache-dtype", "fp8_e4m3",
              "--seed", "0", "--enable-force-include-usage", "--host", "0.0.0.0", "--port", "8000"]
    healthcheck: { <<: *health, test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=4)"], start_period: 900s }
  retrieval:
    build: { context: /path/to/decosa-api, dockerfile: services/retrieval/Dockerfile }
    deploy: { resources: { reservations: { devices: [ { driver: nvidia, device_ids: ["0"], capabilities: [gpu] } ] } } }
    restart: unless-stopped
    environment: { RETRIEVAL_EMBED: qwen3-emb-0.6b, RETRIEVAL_RERANK: qwen3-rr-4b }
    volumes: [retrieval-models:/models]
    healthcheck: { <<: *health, test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8499/health', timeout=4)"], start_period: 600s }
  api:
    image: ${DECOSA_REGISTRY}/decosa-api:${DECOSA_TAG}
    restart: unless-stopped
    depends_on: { llm: { condition: service_healthy }, retrieval: { condition: service_healthy } }
    environment:
      DECOSA_LLM_ROUTE: direct                 # local model; receipts signed by this box's key ("attested")
      DECOSA_LLM_URL: http://llm:8000/v1
      DECOSA_LLM_MODEL: qwen3.8-27b
      DECOSA_RETRIEVAL_URL: http://retrieval:8499
      DECOSA_TARIFF_DATA: /data/tariff          # the ruling set and the index cache live in the data volume
      DECOSA_TARIFF_WARM: "1"                   # build the ruling index at start-up
      DECOSA_LOCAL_SIGNING: "on"
      DECOSA_SIGNER_NAME: ${DECOSA_SIGNER_NAME}
      DECOSA_SESSIONS_PER_IP_HOUR: "1000"
      DECOSA_BUDGET_LLM_TOKENS: "200000"
    ports: ["127.0.0.1:8445:8445"]
    volumes: [decosa-data:/data]
    healthcheck: { <<: *health, test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8445/healthz', timeout=4)"], start_period: 20s }
volumes: { hf-cache: {}, retrieval-models: {}, decosa-data: {} }
```

Use the named volumes exactly as written (a root-owned bind mount breaks `/data`). Run `docker compose up -d --build` and
poll `docker compose ps` until all three are healthy. The first start downloads about 20 GB of LLM weights and 9 GB of
retrieval weights.

## 4. Fetch the ruling set and the HTS

Without this step the API uses the small sample set bundled with it (fine for the smoke test, too small for real use).

```bash
docker compose exec api python scripts/tariff_fetch.py --out /data/tariff   # about 1 hour at 2 requests a second
docker compose restart api                                                   # the index builds at start-up (a few minutes)
curl -s localhost:8445/tariff/info | jq '{ruling_set, hts, index, retrieval}'
```

`ruling_set.count` should be about 2,000 and `retrieval.reachable` true. Re-run the fetch to refresh; the ruling-set hash
and the index hash in every memo then change, which is the point.

## 5. Smoke test

```bash
API=localhost:8445
TOKEN=$(curl -s $API/demo/session -H 'content-type: application/json' -d '{"vertical":"tariff-classification"}' | jq -r .token)
curl -s $API/tariff/classify -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"sample_id":"wall-heater"}' > /tmp/memo.json
jq '{status, code: .proposal.display, reasons, quotes: [.proposal.ruling_citations[] | {ruling, ok}], rulings: [.rulings[].ruling]}' /tmp/memo.json
jq '{record}' /tmp/memo.json | curl -s $API/record/verify -H 'content-type: application/json' -d @- | jq '{ok, summary}'
curl -s $API/tariff/classify -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"sample_id":"vague-bag"}' | jq '{status, reasons}'
```

Pass if the heater memo proposes heading 8516 with at least one verified ruling quote, every receipt is `attested`, the
record verifies, and the vague bag comes back `needs_broker`.

## 6. Use it

- `POST /tariff/classify {"description": "...", "facts": "...", "title": "..."}` returns JSON, or Server-Sent Events with
  `Accept: text/event-stream`. `GET /tariff/info` lists the ruling set, the HTS release, the models and the limits.
- Save `memo_md` and `record` in the product's classification file. Anyone can re-check the record with
  `POST /record/verify` against the key at `GET /attest/signing-key`.
- Keep the API on 127.0.0.1; for other users, put a TLS reverse proxy with authentication in front.

## 7. Keep the direct route

`DECOSA_LLM_ROUTE=gateway` would send prompts (your product description and the rulings) to the hosted Decosa
API. Leave it
off for unreleased products.

Finish with a summary: what is running, the health output, the ruling-set count and index hash, the smoke-test results,
and the reminders above.
Rules and regulations it checks againstDated, linked to the primary source; not legal advice

Regulation watch

Loading the watch status…

4 laws, rules and guidance pages cited; 4 watched nightly at the primary source. A change marks this page for a human re-check; nothing is edited automatically. What we cite and how it is watched

Technical detailsModels, where it runs, labels

In short

Last reviewed

What it is
A research aid for HTS classification: describe a product and it finds CBP rulings on similar products, proposes a heading, subheading and statistical number with the GRI applied, and quotes the rulings word for word. A licensed customs broker decides.
Who it's for
Licensed customs brokers and importers' trade-compliance teams who write classification memos for new products.
Where it runs
Hosted or self-host
Key numbers

On 200 held-out CBP rulings, the memo's top-1 subheading matched CBP's 145 / 200 times, against 57 / 200 for the same model without the rulings; queries were CBP's own descriptions.

  • 145 / 200 Memo top-1 subheading (6-digit) (test split, n = 200)
  • 171 / 200 Memo top-1 heading (4-digit) (test split, n = 200)
  • 172 / 200 Memo top-3 subheading (test split, n = 200)
  • 32.1 s Median end-to-end run, hosted (QA sweep 2026-09-27)
All results, datasets and caveats
Models
Qwen3.8-27B (the proposal); Qwen3 embedder and reranker (the search over rulings)
Where
Hosted or self-host
Checks
Receipt per model call; signed search receipts with the ruling-set index hash; every ruling quote verified word for word with its byte offsets; codes checked against the HTS release; signed hash-chained memo record
Output
Signed record or verdict · Notes, reports and drafts
Data
Confidential business data
Hardware
1× 96 GB GPU
Licence
Permissive (Apache-2.0, MIT)

Questions people ask

Can it do HTS classification for me?

It drafts a memo: a proposed heading, subheading and statistical number, the General Rules of Interpretation applied, and quotes from CBP rulings on similar products. A licensed customs broker or the importer of record decides, and only CBP issues binding rulings.

How accurate is it?

On 200 held-out CBP New York rulings, the top-1 subheading matched CBP's code 145 / 200 times and the heading 171 / 200. The same model with no rulings to read got the subheading 57 / 200. The queries were CBP's own product descriptions, so real product sheets will do worse.

Which rulings does it search?

1,992 New York classification rulings since 2018 from CBP's CROSS database, in 35 headings of 11 chapters, fetched on 27 Sep 2026. The memo record names the ruling set's hash and the index hash, so you can show which version it searched.

How do I know the quotes are real?

Every quote from a ruling is checked in code against the text the search returned, and the memo shows its byte offsets in that ruling. A quote that is not found word for word sends the memo to a broker.

When does it refuse to propose a code?

When the description lacks a fact the code turns on (the outer-surface material, fibre shares by weight, knit or woven, gender), when the closest rulings disagree, when the evidence is thin, or when a quote or code fails a check. It says which.

Ask a question or leave feedbackWe read every message and publish useful answers
Questions & feedback

Ask about Tariff classification memo

We read every message. Questions, comments and our answers show here once we have reviewed and approved them.

Loading questions…

This is a

Plain text. Please leave out personal, patient or client data.

Shown with your message if we publish it. Leave blank to post as “A visitor”.

Nothing appears here until we have read and approved it.