Blog
hedwigaihedwigaihedwigai

Trade

Who imports it? Four workbooks that keep the answer, from US customs records

Marin Mountain Bikes filed 5,711 shipments of bicycles into the US in the twelve months to 12 November 2026. Giant filed 3,943 and Trek 2,121. Nobody announces numbers like these; they are on the bills of lading, the documents that travel with each sea cargo and that US customs keeps, naming who took delivery, from whom, and from which country. We read them through Bill of Lading Data’s partner API, and every figure on this page is theirs.

The question a seller actually asks is narrower than a ranking: who buys what I make, and from whom do they buy it today. Below are five products and their largest importers, a game made of the same rankings, and four workbooks you can make in your account that keep the answer current, each with a schema that refuses an edit which would make it wrong.

572

US companies imported black tea between January and November 2026

6,688

banana shipments filed by Dole Fresh Fruit, the most of any importer

385

olive oil shipments filed by Drupley, almost four times the next importer

Five products and who imports them

Each ranking counts the shipments a consignee filed under one HS code, the customs number for a kind of goods (0803 is bananas, 8712 bicycles). It is a ranking of names as filed, so one firm can appear twice: Dole files as “Dole Fresh Fruit Company, Inc” and as “Dole Fresh Fruit Company”. A consignee is sometimes a forwarder or a warehouse rather than the buyer; Americold Logistics, fourth in olive oil, stores food for others.

US importers of five products, twelve months to 12 November 2026 (black tea from 1 January 2026). Source: Bill of Lading Data.
ProductUS importersLargest three, by shipments
Black tea (HS 0902.30 / 0902.40)572Vahdam Teas Global Inc 397 · FBA Luxmi Estates Inc 119 · Southern Tea LLC 96
Green coffee beans (HS 0901.11)1,533Neumann Gruppe USA, Inc 1,066 · Starbucks Manuf EMEA B V 659 · A La Orden 619
Bananas (HS 0803)622Dole Fresh Fruit Company, Inc 6,688 · Chiquita Fresh North Americallc 6,268 · Del Monte Fresh Produce N A Inc 4,541
Bicycles (HS 8712)286Marin Mountain Bikes Inc 5,711 · Giant Bicycle Inc 3,943 · Specialized Bicycle Components 2,452
Olive oil (HS 1509)477Drupley, Inc 385 · Maeva USA Inc 103 · Borges USA, Inc 96

Guess before you read the table

Two importers of the same product: which filed more? The five rankings are a free game. Sign in to add a product of your own.

Play who imports more

Four workbooks, each with a schema it compiles under

A hedwigai workbook is a document a research run writes and keeps current. Give one a schema and every edit to it, by a run, by you in the app, or by ChatGPT or Claude over MCP, is checked before it is kept. A schema has two parts. A tables layer says what each column holds: a date, a choice, a code of four digits. A TLA+ module says what must hold across rows, and TLC, the TLA+ model checker, checks it on every edit. An edit that breaks either is refused with the row and column it broke.

The rows below are real, read on 12 November 2026. We ran each module through TLC on those rows, where it holds, and on one broken edit, which it refuses.

1. Importer prospects

The largest importers of one HS code, ranked by shipments, with where each buys from. The schema keeps the ranking a ranking — ranks in order with no gap, more shipments never below fewer — and keeps a forwarder, which carries someone else's goods, out of the lead list.

rankimporterhs codeshipmentsships fromkindstatus
1Vahdam Teas Global Inc0902.40397INbuyernew
2FBA Luxmi Estates Inc0902.40119INbuyernew
3Southern Tea LLC0902.4096VN, ID, JM, INbuyernew
4National Management Logistics, Inc0902.4093AR, JM, ES, MXforwardernot a fit
5Henry P. Thomson, Inc0902.4093VN, ID, MY, SGbuyernew
The schema
```tables
{
  "tables": [
    {
      "name": "importers",
      "tab": "Importers",
      "key": [
        "importer"
      ],
      "columns": [
        {
          "name": "rank",
          "type": "integer",
          "required": true,
          "unique": true,
          "min": 1
        },
        {
          "name": "importer",
          "required": true,
          "unique": true
        },
        {
          "name": "hs code",
          "required": true,
          "pattern": "^[0-9]{4}(\\.[0-9]{2}){0,3}$"
        },
        {
          "name": "shipments",
          "type": "integer",
          "required": true,
          "min": 0
        },
        {
          "name": "ships from",
          "type": "list",
          "separator": ", "
        },
        {
          "name": "kind",
          "type": "choice",
          "values": [
            "buyer",
            "forwarder",
            "unsure"
          ],
          "required": true
        },
        {
          "name": "status",
          "type": "choice",
          "values": [
            "new",
            "contacted",
            "replied",
            "meeting",
            "won",
            "not a fit"
          ],
          "required": true
        }
      ]
    }
  ]
}
```

```tla
---- MODULE Business ----
EXTENDS Workbook, Naturals, Sequences
VARIABLE ok
Init == ok = TRUE
Next == UNCHANGED ok
Spec == Init /\ [][Next]_ok
\* The ranking reads top down: rank 1, 2, 3 ... with no gap.
RanksInOrder == \A i \in DOMAIN importers : importers[i].rank = i
\* More shipments never sit below fewer.
ByShipments == \A i \in 1..(Len(importers) - 1) :
                  importers[i].shipments >= importers[i + 1].shipments
\* A forwarder carries someone else's goods: it is never worked as a lead.
ForwardersAreNotLeads == \A i \in DOMAIN importers :
                  importers[i].kind = "forwarder" => importers[i].status = "not a fit"
====
```

```cfg
SPECIFICATION Spec
INVARIANT RanksInOrder
INVARIANT ByShipments
INVARIANT ForwardersAreNotLeads
```

Refused by TLC: setting row 4’s status to “contacted” breaks RanksInOrder, ByShipments, ForwardersAreNotLeads.

2. Supplier switch watch

One importer's bills of lading, oldest first, with the country each was shipped from. `switched` is not an opinion: TLC checks that it is true exactly where a bill's origin differs from the bill before it. Harney & Sons bought black tea from India in February and from Taiwan in July.

importerbill of ladingarrivedhs codesupplieroriginswitched
Harney & Sons Tea(Kolkata, unnumbered)2026-02-2509024010Aeon Commodities Private LimitedINno
Harney & Sons TeaGPBBTPNYC2652482026-07-01090240Jen Ming Ritz Co., Ltd.TWyes
Harney & Sons TeaGPBBTPNYC2672742026-09-10090240Jen Ming Ritz Co., Ltd.TWno
The schema
```tables
{
  "tables": [
    {
      "name": "watch",
      "tab": "Watch",
      "key": [
        "importer",
        "bill of lading"
      ],
      "columns": [
        {
          "name": "importer",
          "required": true
        },
        {
          "name": "bill of lading",
          "required": true,
          "unique": true
        },
        {
          "name": "arrived",
          "type": "date",
          "required": true
        },
        {
          "name": "hs code",
          "required": true,
          "pattern": "^[0-9]{4,10}$"
        },
        {
          "name": "supplier",
          "required": true
        },
        {
          "name": "origin",
          "required": true,
          "pattern": "^[A-Z]{2}$"
        },
        {
          "name": "switched",
          "type": "boolean",
          "required": true
        }
      ]
    }
  ]
}
```

```tla
---- MODULE Business ----
EXTENDS Workbook, Naturals, Sequences
VARIABLE ok
Init == ok = TRUE
Next == UNCHANGED ok
Spec == Init /\ [][Next]_ok
\* Bills are kept oldest first, one importer at a time.
InOrder == \A i \in 1..(Len(watch) - 1) :
             watch[i].importer = watch[i + 1].importer => watch[i].arrived <= watch[i + 1].arrived
\* "switched" is not an opinion: it says whether this bill's origin differs
\* from the same importer's bill before it.
SwitchedMeansNewOrigin == \A i \in DOMAIN watch :
    watch[i].switched =
      (\E j \in DOMAIN watch : j = i - 1 /\ watch[j].importer = watch[i].importer
                               /\ watch[j].origin /= watch[i].origin)
====
```

```cfg
SPECIFICATION Spec
INVARIANT InOrder
INVARIANT SwitchedMeansNewOrigin
```

Refused by TLC: setting row 2’s switched to “no” breaks InOrder, SwitchedMeansNewOrigin.

3. A rival exporter's customers

Who a competing exporter ships to, read from the exporter's side of the same records, with when each customer was first and last seen. A customer seen once was seen on one day.

exportercustomercustomer cityfirst seenlast seenshipments
Jen Ming Ritz Co., Ltd.Harney & Sons TeaMillerton, NY2026-07-012026-09-102
Casa Fuentes SACIFIEastern Tea Corp.Jamesburg, NJ2026-10-192026-10-191
Madhu Jayanti InternationalHarris Tea CompanyMoorestown, NJ2026-09-192026-09-191
The schema
```tables
{
  "tables": [
    {
      "name": "customers",
      "tab": "Customers",
      "key": [
        "exporter",
        "customer"
      ],
      "columns": [
        {
          "name": "exporter",
          "required": true
        },
        {
          "name": "customer",
          "required": true
        },
        {
          "name": "customer city"
        },
        {
          "name": "first seen",
          "type": "date",
          "required": true
        },
        {
          "name": "last seen",
          "type": "date",
          "required": true
        },
        {
          "name": "shipments",
          "type": "integer",
          "required": true,
          "min": 1
        }
      ]
    }
  ]
}
```

```tla
---- MODULE Business ----
EXTENDS Workbook, Naturals, Sequences
VARIABLE ok
Init == ok = TRUE
Next == UNCHANGED ok
Spec == Init /\ [][Next]_ok
\* A customer is first seen no later than it is last seen.
SeenInOrder == \A i \in DOMAIN customers : customers[i].first_seen <= customers[i].last_seen
\* One shipment can only have been seen on one day.
OneShipmentOneDay == \A i \in DOMAIN customers :
    customers[i].shipments = 1 => customers[i].first_seen = customers[i].last_seen
====
```

```cfg
SPECIFICATION Spec
INVARIANT SeenInOrder
INVARIANT OneShipmentOneDay
```

Refused by TLC: setting row 2’s first seen to “2026-10-01” breaks SeenInOrder, OneShipmentOneDay.

4. Outreach to importers

The importers worth a message, why each, and how far the conversation got. A stage past `new` must say when the importer was contacted, and a reply must come after the contact.

importerwhy themstagecontacted onreplied onsource
Marin Mountain Bikes Inc5,711 bicycle shipments in 12 months, from ID and VNnewhttps://billofladingdata.com/
Giant Bicycle Inc3,943 bicycle shipments, from VN and TWnewhttps://billofladingdata.com/
Trek Bicycle Corporation2,121 bicycle shipments, from VN and CNnewhttps://billofladingdata.com/
The schema
```tables
{
  "tables": [
    {
      "name": "outreach",
      "tab": "Outreach",
      "key": [
        "importer"
      ],
      "columns": [
        {
          "name": "importer",
          "required": true,
          "unique": true
        },
        {
          "name": "why them",
          "required": true
        },
        {
          "name": "stage",
          "type": "choice",
          "values": [
            "new",
            "contacted",
            "replied",
            "meeting",
            "won",
            "lost"
          ],
          "required": true
        },
        {
          "name": "contacted on",
          "type": "date"
        },
        {
          "name": "replied on",
          "type": "date"
        },
        {
          "name": "source",
          "type": "url",
          "required": true
        }
      ]
    }
  ]
}
```

```tla
---- MODULE Business ----
EXTENDS Workbook, Naturals, Sequences
VARIABLE ok
Init == ok = TRUE
Next == UNCHANGED ok
Spec == Init /\ [][Next]_ok
Past(s) == s \in {"contacted", "replied", "meeting", "won", "lost"}
Answered(s) == s \in {"replied", "meeting", "won"}
\* A stage past "new" says when the importer was contacted.
ContactedHasADate == \A i \in DOMAIN outreach :
    Past(outreach[i].stage) <=> outreach[i].contacted_on /= ""
\* A reply has a date, and it comes after the contact.
RepliesFollowContact == \A i \in DOMAIN outreach :
    Answered(outreach[i].stage) =>
      /\ outreach[i].replied_on /= ""
      /\ outreach[i].replied_on >= outreach[i].contacted_on
====
```

```cfg
SPECIFICATION Spec
INVARIANT ContactedHasADate
INVARIANT RepliesFollowContact
```

Refused by TLC: setting row 1’s stage to “contacted” breaks ContactedHasADate, RepliesFollowContact.

Keeping them current

In a workbook’s run, or in your own agent over MCP, the trade program reads the same records. A company’s page is read once and kept for thirty days for every account, so a second question about it is free; Bill of Lading Data is asked only for what that page cannot answer, such as an HS code on each line or the importers of a code, and is billed per record it returns.

$ trade search importers 0902.40            # who imports black tea, ranked
$ trade company shipments harney-sons-tea --hs 0902 --from 2026-01-01
$ trade supplier customers jen-ming-ritz      # a rival exporter's customers

What these records cannot say

They cover sea imports into the US; goods that come by air, truck or rail are mostly absent. A consignee can be a forwarder, a warehouse or a marketplace (Amazon.com Services is eighth in black tea), so a ranking is a list of names to check, not of buyers. A filer can mark its records confidential, and some of the largest importers do. We leave out shipment value: the records carry an estimate on too few lines for a ranking by it to mean anything, so the rankings are by shipments.

More head-to-head evals

GLM-5.3 against DeepSeek V4.1 Flash on 75 questionsTwo models answer the same 75 questions; one is always right, the other 14× cheaper.
MeasureGLM-5.3DeepSeek V4.1 Flash
Right, of 757571
Seconds a question, median3.91.8
Read the comparison
How we made a long-running AI agent for knowledge work 4× fasterOne cited knowledge work task through 13 experiments: 937 s to 233 s, with the model held constant.
MeasureFirst runBest since
Landscape, seconds937233
Memo, seconds759602
Read the report
Ten models plan a change to a spreadsheet: which do it in under a minute, traced call by callTen models drive one agent harness through the same spreadsheet request; six write the plan, timed and traced call by call.
MeasureQwen3 235BGLM-5.2Gemini 3.8 FlashClaude Opus 5
Seconds to the plan, median27485794
Plans written, of three runs2333
Read the comparison
SubQ, jev, Perplexity's Decider and Bespoke's Nimble on biotech diligence memos: finding the sentences a column is aboutFour decision models judge every sentence of ten biotech diligence memos for five columns, scored against sentences a person marked.
MeasureSubQjevDeciderNimble
Found, mean of five columns at section length64%65%66%68%
Mechanism of action, found at section length57%51%61%54%
Read the comparison
Leads with a reason to write: hedwigai ask, Apollo and treg on one brief40 leads each, every one checked: the right person, an inbox that accepts, and a dated reason to write.
MeasureaskApollotreg
Usable leads13 of 407 of 406 of 28
Cost per usable lead$0.037$0.27$0.23
Read the comparison
Self-hosted decisions for ask: Perplexity's open Decider in place of jev on 92 company questionsThe decisions inside hedwigai ask made by Perplexity's open Decider on two NVIDIA RTX 3090s, against jev.
Measureask with jevask with Decider
Questions right, of 928989
Wrong answers12
Read the comparison
Clinical decisions: jev and ask on System One, PubMedQA, SciFact and a 370-decision setEdgeEvals' System One board, PubMedQA, SciFact, and a clinical set built to System One's rules.
Measurejevask
Our 370 clinical decisions, right94.6%93.0%
PubMedQA, right (best EdgeEvals scored: 62.2%)77.0%74.4%
Read the comparison
Executive lookup for US-listed companies: Claude Opus 5.5 and ask on 95 questionsWho runs a public company, and how to email them: a frontier model from memory against hedwigai ask.
MeasureaskClaude Opus 5.5
Questions right, of 955963
Small companies right, of 20139
Read the comparison
Rental comps: floor-plan rents and amenities on 14 listings, five assistants comparedRent per floor plan and amenities for US rentals: hedwigai realestate-us against Claude, GPT, Gemini and Perplexity with web search.
Measurerealestate-usBest of four assistants
Floor-plan rents right, of 262623
Amenities claimed the listing doesn't state12129
Read the comparison
Company screening from SEC filings: jev, Vela 2.0 and ask on 92 yes/no questionsYes-or-no questions about public companies, answered by two decision models alone and by hedwigai ask.
Measureaskjev
Right, of the questions answered98%79%
Wrong answers, of 92219
Read the comparison
Defect localization in industrial images: four frontier models and a trained localizerFour frontier models asked to put a box around what they just described.
MeasureTrained localiserBest of four frontier models
Mean overlap with the defect (IoU, 0 to 1)0.4210.109
Read the comparison