Customs Ruling Finder, HTS Classification Precedent avatar

Customs Ruling Finder, HTS Classification Precedent

Pricing

Pay per usage

Go to Apify Store
Customs Ruling Finder, HTS Classification Precedent

Customs Ruling Finder, HTS Classification Precedent

Search CBP customs rulings by product or tariff code, keyless. See which HTS code Customs actually assigned, read the full ruling, and know whether it was revoked or modified before you cite it. Codes are reformatted automatically so tariff-schedule codes actually match.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Ken M

Ken M

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Categories

Share

Before you classify a product, find out how CBP has actually classified it before. Search US Customs rulings by product name or tariff code, see which HTS code Customs assigned, read the full ruling, and know whether that ruling still stands.

No login, no API key, no proxy. The actor reads the official keyless CROSS API, so runs are fast and cheap.

Two things this gets right that the raw API does not

1. Your tariff code will not match unless it is reformatted

CROSS groups a ten-digit code 4.2.4. The Harmonized Tariff Schedule groups the same ten digits 4.2.2.2. Search matches the term literally, so:

Search termFormatHits
6109.10.0040CROSS121
6109.10.00.40HTS0
6109100040digits only0

Same code, same product, three very different answers. Copy a code out of the tariff schedule and the honest-looking result is "no rulings exist."

This actor reformats codes before searching and tells you it did:

Reformatted "6109.10.00.40" to "6109.10.0040" for CROSS, which matches codes literally.
"6109.10.0040" matched 121 rulings.

Every row then comes back in both notations, so it joins cleanly against tariff data either way:

{
"tariffs": ["6110.20.2079", "6109.10.0040", "9903.01.25"],
"tariffsHtsFormat": ["6110.20.20.79", "6109.10.00.40", "9903.01.25"]
}

2. Revoked rulings come back looking exactly like live ones

A customs ruling is legal authority. CROSS returns revoked and modified rulings inline with current ones, with nothing in the search result to tell them apart. Citing a dead ruling to justify a classification is not a cosmetic error, it is the kind an importer gets penalised for.

Every row carries its precedent status and names whatever superseded it:

{
"rulingNumber": "K88339",
"subject": "The tariff classification of a laptop computer/television from China.",
"rulingDate": "2004-08-17",
"precedentStatus": "revoked",
"isSuperseded": true,
"supersededBy": ["W967655"],
"precedentStatusNote": "Superseded. Read the revoking or modifying ruling before citing this one."
}

Set onlyCurrentPrecedent to drop them entirely. Filtered rows are never charged for.

noRecordedChange is deliberately not called "good law". It states what the data supports: CROSS records no revoking or modifying document. CBP can supersede a ruling by an action CROSS has not linked, so absence of a link is not proof the ruling still stands.

What you get

One row per ruling.

FieldDescription
rulingNumbere.g. N160415, H305865
subjectWhat the ruling decided
categoriese.g. Classification, Origin, Marking
rulingDateYYYY-MM-DD, or null where CBP recorded none
rulingDateMissingtrue when the date is absent, so you can filter instead of guess
yearFromDocumentPathYear inferred from where CBP filed the document. Often the only dating on an undated ruling
collection / collectionLabelny (National Commodity Specialist Division) or hq (Headquarters)
tariffsHTS codes CBP assigned, in CROSS notation
tariffsHtsFormatThe same codes in tariff-schedule notation
tariffDigits / primaryTariffDigits only, and the first assigned code
precedentStatusnoRecordedChange, modified or revoked
isSuperseded / supersededByQuick filter, and which rulings replaced this one
revokedBy / revokes / modifiedBy / modifiesThe full precedent chain in both directions
relatedRulingsRulings CBP cross-references
isUsmca / isNaftaTrade agreement rulings
rulingUrl / documentUrlThe ruling page and the source document, both absolute
fullText / fullTextCharsComplete ruling body when requested, null when not
matchedSearchTerm / queryUsedWhat you asked for, and what was actually sent
scrapedAtRun timestamp, ISO 8601

Dates that are not dates

About 1% of rulings carry 0001-01-01 as their date, meaning CBP recorded no date. These are real rulings with full text, not placeholders. Published as-is they sort as year 1 and quietly poison any date filter, so they come back as null with rulingDateMissing: true, and yearFromDocumentPath gives you something to sort on. That value is inferred from the file path, never merged into rulingDate, because it is not what CBP stated.

Input

FieldDescription
searchTermsProduct names or tariff codes. Codes are reformatted automatically
rulingNumbersFetch specific rulings directly. These always return full text
collectionALL, ny or hq
sortByDATE_DESC (default) or RELEVANCE
dateFrom / dateToYYYY-MM-DD. Applied after fetching, see below
onlyCurrentPrecedentDrop revoked and modified rulings
includeFullTextFetch the complete ruling body
newOnlyMonitor mode: only rulings not seen in earlier runs
maxRowsStop after N rulings (default 100)

Newest-first is the default because relevance order returns 1990s rulings ahead of current ones. DATE_ASC is not offered: the API accepts it but sorts every undated ruling to the front.

Monitor mode

Set newOnly and run it on a schedule to watch for new precedent on your products. With newest-first sorting it stops at the first ruling it already knows, so a quiet week costs nothing rather than re-walking decades of history. A ruling backfilled with an older date after a previous run will be missed; use relevance sort to sweep the whole result set instead.

Examples

Has CBP ruled on anything classified under my code?

{ "searchTerms": ["6109.10.00.40"], "maxRows": 50 }

Current precedent only, with the reasoning

{
"searchTerms": ["integrated solar panel"],
"onlyCurrentPrecedent": true,
"includeFullText": true,
"dateFrom": "2020-01-01"
}
{
"rulingNumber": "N160415",
"subject": "The tariff classification of an Integrated Solar Panel from an unspecified country of origin.",
"rulingDate": "2011-05-06",
"collectionLabel": "National Commodity Specialist Division (New York)",
"tariffs": ["8501.61.0000"],
"tariffsHtsFormat": ["8501.61.00.00"],
"precedentStatus": "noRecordedChange",
"isSuperseded": false,
"rulingUrl": "https://rulings.cbp.gov/ruling/N160415",
"documentUrl": "https://rulings.cbp.gov/docs/ny/2011/n160415.doc",
"fullTextChars": 3512
}

Watch for new rulings on your products

{ "searchTerms": ["lithium battery", "8507.60.0020"], "newOnly": true, "sortBy": "DATE_DESC" }

Who it's for

Customs brokers and trade compliance teams classifying goods, importers checking a code before they file, trade attorneys researching precedent and building protest arguments, and sourcing teams pricing landed cost who need the classification to hold up.

Pricing

Pay per ruling. The first 3 rows of every run are free so you can validate the output before you pay. Full text costs an extra upstream request per ruling and is charged at the higher rate. Rows removed by your filters are never charged.

Limits worth knowing

  • This is not customs advice, and a ruling is only binding on the party who requested it. Use CROSS to find how CBP has reasoned about similar goods, then get your own ruling if the classification is material.
  • noRecordedChange is not a clean bill of health. It means CROSS links no revoking or modifying document. Verify before relying on it.
  • There is no server-side date filter. The API accepts a date parameter and ignores it, so filtering happens after fetching. Rulings with no recorded date are excluded from a date-filtered run, since they cannot be placed in the window.
  • Rulings often cite Chapter 99 codes such as 9903.01.25 alongside the classification. Those are the Section 301 and IEEPA headings; read them with the Import Duty & Tariff Calculator in additionalTariffs mode.
  • A missing value is always null, never 0 or an empty string.