HS Code Lookup API avatar

HS Code Lookup API

Pricing

from $4.00 / 1,000 tariff results

Go to Apify Store
HS Code Lookup API

HS Code Lookup API

Unofficial HS code lookup API: find the UK commodity code or US HTS code for a product description or a code, with the official description, the 6-digit HS code and the duty rate. Reads the public UK Trade Tariff and the US Harmonized Tariff Schedule.

Pricing

from $4.00 / 1,000 tariff results

Rating

0.0

(0)

Developer

Sourcing Data Studio

Sourcing Data Studio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

15 days ago

Last modified

Share

Classify products for customs and quote duty before you ship. HS Code Lookup API turns a product description or a code into the national tariff code, the official description, the shared 6-digit HS code and the import duty rate, from the UK Trade Tariff and the US Harmonized Tariff Schedule. Use it for product lists, landed-cost sheets and AI agents. You pay only for results saved: $0.005 per tariff result on the Free tier, so a typical query (one product description on the UK tariff, 5 candidate codes) costs $0.025.

Unofficial: not affiliated with or endorsed by HM Revenue & Customs, the UK Department for Business and Trade or the U.S. International Trade Commission.

Contains public sector information licensed under the Open Government Licence v3.0.

Source: Harmonized Tariff Schedule of the United States, U.S. International Trade Commission.

Importers, sourcing agents and e-commerce sellers use this HS code lookup API before they quote a landed cost: one run is an HS code finder, a UK commodity code lookup, a tariff code lookup and an import duty rate lookup for a whole product list. It also works as a harmonized system code search and a customs classification API for AI agents and spreadsheets, and as an HTS code lookup when you add the US tariff.

What does HS Code Lookup API do?

  • Looks up a code of 4, 6, 8 or 10 digits directly and returns the matching tariff line, then the declarable lines below it.
  • Searches a product description in the tariff, up to your Maximum results per entry. For the UK it returns the lines that the UK tariff search ranks best. For the US it returns only lines whose description contains every word of your entry; when no line does, the entry gets no result and nothing is charged.
  • Returns the duty as published and, when it is a plain percentage, also as a number you can calculate with.
  • Never guesses a duty. When the UK rate depends on an additional code, or only a duty under authorised use is published, dutyRate stays empty and dutyNote lists the published rates.
  • Returns the UK import VAT rate for UK results.
  • Returns the parent descriptions, so you can check that the line fits your product: from the chapter down for the UK, from the 4-digit heading down for the US.
  • Charges one event per saved result. Failed requests, duplicates and entries with no result are never charged.
  • Says so when entries were not read, in the status message and in the run summary, instead of reporting that nothing matched.
  • Writes a run summary with totals, the status of every entry, failures, the licence lines and your feedback to the key-value store.

Three things to keep in mind:

  • The duty shown is the basic UK rate or US rate of the tariff line, not the full duty at the border: preferences and additional duties are not in it (see the Limits section below). It is not the duty of any other country.
  • The first 6 digits of a code are shared internationally. The remaining digits are national, so a UK 10-digit code is not valid in the US and the other way round.
  • A classification found by text search is a starting point, not a customs ruling. Read the description and the parent lines, and confirm with your customs broker or the customs authority before you declare goods.

Output example

These rows come from a real run.

[
{
"searchQuery": "8517130000",
"tariff": "UK",
"code": "8517130000",
"hs6": "851713",
"description": "Smartphones",
"hierarchy": [
"Electrical machinery and equipment and parts thereof; sound recorders and reproducers, television image and sound recorders and reproducers, and parts and accessories of such articles",
"Telephone sets, including smartphones and other telephones for cellular networks or for other wireless networks; other apparatus for the transmission or reception of voice, images or other data, including apparatus for communication in a wired or wireless network (such as a local or wide area network), other than transmission or reception apparatus of heading 8443 , 8525 , 8527 or 8528",
"Telephone sets, including smartphones and other telephones for cellular networks or for other wireless networks"
],
"dutyRate": "0.00 %",
"dutyNote": null,
"dutyRatePercent": 0,
"vatRate": "20.00 %",
"units": "p/st",
"isDeclarable": true,
"matchType": "code",
"url": "https://www.trade-tariff.service.gov.uk/commodities/8517130000",
"source": "trade-tariff.service.gov.uk",
"retrievedAt": "2026-10-05T15:16:00.325Z"
},
{
"searchQuery": "phone case",
"tariff": "UK",
"code": "4202911010",
"hs6": "420291",
"description": "Hand-made",
"hierarchy": [
"Articles of leather; saddlery and harness; travel goods, handbags and similar containers; articles of animal gut (other than silkworm gut)",
"Trunks, suitcases, vanity cases, executive-cases, briefcases, school satchels, spectacle cases, binocular cases, camera cases, musical instrument cases, gun cases, holsters and similar containers; travelling-bags, insulated food or beverages bags, toilet bags, rucksacks, handbags, shopping-bags, wallets, purses, map-cases, cigarette-cases, tobacco-pouches, tool bags, sports bags, bottle-cases, jewellery boxes, powder boxes, cutlery cases and similar containers, of leather or of composition leather, of sheeting of plastics, of textile materials, of vulcanised fibre or of paperboard, or wholly or mainly covered with such materials or with paper",
"Other",
"With outer surface of leather or of composition leather",
"Travelling-bags, toilet bags, rucksacks and sports bags"
],
"dutyRate": "2.00 %",
"dutyNote": null,
"dutyRatePercent": 2,
"vatRate": "20.00 %",
"units": null,
"isDeclarable": true,
"matchType": "text",
"url": "https://www.trade-tariff.service.gov.uk/commodities/4202911010",
"source": "trade-tariff.service.gov.uk",
"retrievedAt": "2026-10-05T15:16:05.230Z"
},
{
"searchQuery": "phone case",
"tariff": "UK",
"code": "4202911090",
"hs6": "420291",
"description": "Other",
"hierarchy": [
"Articles of leather; saddlery and harness; travel goods, handbags and similar containers; articles of animal gut (other than silkworm gut)",
"Trunks, suitcases, vanity cases, executive-cases, briefcases, school satchels, spectacle cases, binocular cases, camera cases, musical instrument cases, gun cases, holsters and similar containers; travelling-bags, insulated food or beverages bags, toilet bags, rucksacks, handbags, shopping-bags, wallets, purses, map-cases, cigarette-cases, tobacco-pouches, tool bags, sports bags, bottle-cases, jewellery boxes, powder boxes, cutlery cases and similar containers, of leather or of composition leather, of sheeting of plastics, of textile materials, of vulcanised fibre or of paperboard, or wholly or mainly covered with such materials or with paper",
"Other",
"With outer surface of leather or of composition leather",
"Travelling-bags, toilet bags, rucksacks and sports bags"
],
"dutyRate": "2.00 %",
"dutyNote": null,
"dutyRatePercent": 2,
"vatRate": "20.00 %",
"units": null,
"isDeclarable": true,
"matchType": "text",
"url": "https://www.trade-tariff.service.gov.uk/commodities/4202911090",
"source": "trade-tariff.service.gov.uk",
"retrievedAt": "2026-10-05T15:16:05.230Z"
}
]

What data can you get with HS Code Lookup API?

FieldTypeDescription
searchQuerystringThe entry from your input that produced this row: a product description or a code.
tariffstringThe national tariff the row comes from: UK (UK Trade Tariff) or US (US Harmonized Tariff Schedule).
codestringNational code as the source prints it. UK: always 10 digits, padded with zeros (8517130000; heading 8517 is 8517000000). US: dotted, with the length of its level (8517, 8517.13, 8517.13.00 or 8517.13.00.00).
hs6stringFirst 6 digits of the code, the part shared internationally. Null for rows at heading level, which have no 6-digit code.
descriptionstringOfficial description of the tariff line, as published.
hierarchyarrayDescriptions of the parent lines from the top down: from the chapter for the UK, from the 4-digit heading for the US (the US data has no chapter text; its text-only grouping lines are included). Does not include the row itself. Empty for a US heading row.
dutyRatestringImport duty as published. UK: the third-country rate, which applies to goods from any country without a trade preference; preferences and additional duties are not included (for example 0.00 %). US: the general (column 1) rate only (for example Free or 4.6%); the additional duties of Chapter 99 (Section 301, Section 232, IEEPA-based duties and others) are not included and change often, and the HTS text can still list measures that have ended. Null when the source shows no rate for this line, or no single rate for all goods (see dutyNote).
dutyNotestringFilled only when dutyRate is null although the source shows a rate: the UK duty depends on an additional code (the note lists every rate with its code), or only a duty under authorised use is published. Null in every other case.
dutyRatePercentnumberThe duty as a number when it is a plain percentage; Free is 0. Null for rates by weight or quantity, for mixed rates and when dutyRate is null.
vatRatestringUK import VAT as published (for example 20.00 %). Always null for the US.
unitsstringSupplementary unit or unit of quantity as published (for example p/st for the UK, No. for the US). Null when none is shown.
isDeclarablebooleanTrue when goods can be declared under this exact code, false for a grouping line with more detailed codes below it, null when the source does not tell.
matchTypestringHow the row was found: code when your entry was a code, text when it was a product description.
urlstringPublic page for the code on the source site.
sourcestringSource site domain: trade-tariff.service.gov.uk or hts.usitc.gov.
retrievedAtstringISO 8601 time (UTC) when the row was read from the source.

UK data comes from the public UK Trade Tariff service at trade-tariff.service.gov.uk under the Open Government Licence v3.0. If you look for a UK Trade Tariff API with a simple row format, this Actor reads the same public data. US data comes from the search service of the Harmonized Tariff Schedule.

How to use HS Code Lookup API

  1. Click Try for free on this page.
  2. In Products or codes to look up, enter one product description or one code per line. Use plain product words such as "laptop" or "usb cable", not brand names or model numbers.
  3. In Tariffs to search, keep UK, or add US to get one set of results per tariff.
  4. Set Maximum results per entry (1 for the top match only, 5 or more to compare candidate codes) and Maximum results in total.
  5. Click Start. Results appear in the Output tab while the run is going.
  6. Download the results as JSON, CSV or Excel, or read them through the API.

Tips for better matches:

  • A declarable 10-digit code gives one result. A 10-digit grouping code also returns the lines below it, up to your maximum per entry. A 4-digit or 6-digit code gives the grouping line first, then the declarable lines below it.
  • If a description returns lines that do not fit, try the generic product name or the 4-digit heading code.
  • The US tariff has its own wording. If a description gives no US result, use fewer or more general words, or first find the 6-digit HS code with a UK text search and then run that code against the US tariff.
  • Write codes as text. A code sent as a JSON number is accepted, but a number cannot keep a leading zero.

Input examples

Quick test

{
"searchQueries": [
"8517130000",
"phone case",
"usb cable",
"laptop"
],
"tariffs": [
"UK"
],
"maxResultsPerQuery": 2,
"maxItems": 10
}

Two tariffs by code

{
"searchQueries": [
"851713",
"8471300000",
"4202.12"
],
"tariffs": [
"UK",
"US"
],
"maxResultsPerQuery": 3,
"maxItems": 30
}

Compare candidate codes

{
"searchQueries": [
"lithium battery",
"headphones",
"usb cable"
],
"tariffs": [
"UK"
],
"maxResultsPerQuery": 8,
"maxItems": 24,
"feedback": "Example: please add the EU tariff."
}

Run summary

Every run writes a RUN_SUMMARY record to the key-value store (open the Storage tab). Its listings object has one entry per input entry and tariff, for example UK:phone case, with a status, the number of saved items and, when something is missing, a note. The statuses are:

  • ok: the entry was read completely and its results were saved, up to your result limits.
  • partial: some results were saved, but others are missing: a request for part of the entry failed or was refused, or a candidate was dropped because it was incomplete. The note says which. Run the entry again to get the rest.
  • no-match: the source answered and has no line for the entry, or the code does not exist. Nothing is saved or charged.
  • blocked: the source refused the request, or the run stopped asking the source after it refused several requests. Nothing is saved or charged.
  • failed: a request failed because the source did not answer or answered with an error. Nothing is saved or charged.
  • stopped: the entry was not read, or not saved, because the run stopped first: result limit, maximum cost, time limit, an abort, or a source that did not answer. After a restart of the run it can also be an entry that was read before the restart.
  • not-read: the entry was not read to the end, and nothing stopped the run: the run ended early. This says nothing about the entry or the source. Nothing is saved or charged for it. Run this entry again.

A run that saved nothing and has an entry in the state not-read ends as failed. A run that saved results and has such entries ends as succeeded, and its status message names them.

Apify can restart a run, for example when it moves the run to another server. HS Code Lookup API does not keep the states of the entries across a restart, so it cannot tell which entries were read before it. After a restart no entry is marked not-read. The entries that were not finished get the status stopped and a note that says it is not known whether they were read before the restart. A restarted run does not end as failed because of "No results saved", since results may have been saved before the restart, and its message never says that nothing was charged.

The status message of a restarted run says that the run was restarted. When you pay per event, it gives the number of results the run has saved in all, before and after the restart. That number is the charging count that Apify keeps for the run. When you do not pay per event, no count is known, and the message says that the results saved before the restart are in the dataset. The message also says how many entries were not finished after the restart. They may have been read before the restart, or not read at all. The numbers in the run summary (totals and billing) count only the part of the run after the restart.

After a restart HS Code Lookup API does not always go on with the entries that were still to be read, because it does not know where the run was. Compare the results in the dataset with your entries, and start a new run with only the entries that have no result. If your Maximum cost per run was already used up when the run restarted, the message gives the number of results saved in all and says that the run stopped at your maximum cost per run.

The summary also holds the totals (among them extraPasses and requestsLeft, see the Limits section), every failed request (with its HTTP status, never the page), the licence lines and your feedback.

How much does HS Code Lookup API cost?

EventYou pay forFreeBronzeSilverGold
Tariff resultOne tariff classification result (code, description, duty) saved to the dataset. Failed requests, duplicates and queries with no result are never charged.$0.005$0.005$0.0045$0.004

Prices are per event. Your tier follows your Apify subscription plan.

On the Free tier, $0.005 per tariff result means:

  • 100 tariff results cost $0.50.
  • 1,000 tariff results cost $5.00.
  • A typical query (one product description on the UK tariff, 5 candidate codes) is 5 tariff results and costs $0.025.

Apify also charges a tiny start fee per run (apify-actor-start, $0.00005 per GB of memory). Failed requests, duplicates and entries with no result are never charged. A run saves at most 2,000 results (Maximum results in total), so the results of one run cost at most $10 on the Free tier (2,000 x $0.005), plus the start fee. To cap your spending lower, set Maximum cost per run in the run options; the run stops cleanly when that budget is used.

Use HS Code Lookup API from the API, n8n, Make or an AI agent

Apify API

Run the Actor and get the tariff results in one call:

curl

curl -X POST "https://api.apify.com/v2/acts/sourcing-data-studio~hs-code-lookup-api/run-sync-get-dataset-items" \
-H "Authorization: Bearer YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"searchQueries":["8517130000","phone case","usb cable","laptop"],"tariffs":["UK"],"maxResultsPerQuery":2,"maxItems":10}'

JavaScript (Node.js, npm install apify-client)

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const input = {
"searchQueries": [
"8517130000",
"phone case",
"usb cable",
"laptop"
],
"tariffs": [
"UK"
],
"maxResultsPerQuery": 2,
"maxItems": 10
};
const run = await client.actor('sourcing-data-studio/hs-code-lookup-api').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

Python (pip install "apify-client>=3", Python 3.11 or later)

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"searchQueries": [
"8517130000",
"phone case",
"usb cable",
"laptop"
],
"tariffs": [
"UK"
],
"maxResultsPerQuery": 2,
"maxItems": 10
}
run = client.actor("sourcing-data-studio/hs-code-lookup-api").call(run_input=run_input)
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item)

n8n and Make

Use an HTTP request module with the same URL, method POST and a JSON body like the input examples above. Store your Apify API token as a credential, never in the workflow itself.

AI agents through the Apify MCP server

Connect your agent to the Apify MCP server and call sourcing-data-studio/hs-code-lookup-api with the same input. The agent gets one row per candidate code and can compare the descriptions and parent lines. Agents can use the Feedback for the developer field to report missing fields or problems.

For AI agents

  • Minimal input: {"searchQueries": ["phone case"], "maxResultsPerQuery": 3}. It returns at most 3 UK tariff results, which cost at most $0.015 at the Free price of $0.005 per result, plus the platform's start fee.
  • Read first: code, hs6, description, dutyRate and dutyNote. A null dutyRate is not a zero duty: dutyNote says why there is no single rate.
  • Outcomes live in the run status message and in the RUN_SUMMARY record of the run's key-value store. Its listings object holds one status per entry and tariff: ok, no-match, partial, blocked, failed, stopped or not-read. A note says why a result is missing.
  • Do not use it for the duty of another country, for the full duty at the border (preferences and additional duties are not in dutyRate), or as a binding customs classification.
  • It is callable through the Apify MCP server as the tool sourcing-data-studio/hs-code-lookup-api and through the Apify API.

Limits

  • At most 100 entries and 2,000 results per run, and at most 20 results per entry and tariff.
  • The Actor sends one request at a time, about one every 3 seconds, because the public services publish no rate limit. A code needs one request per tariff. A description needs one search request plus one request per heading found, at most one more per result. Twenty entries take about one to three minutes.
  • The public UK service is offered by its publisher as a way to try the tariff data. If a service refuses a request with HTTP 401, 403 or 407, or answers with a page instead of data, the Actor does not ask for that page again. The entry is marked blocked in the run summary and nothing is charged for it.
  • After HTTP 429 (too many requests) the Actor waits as long as the service asks (the Retry-After header, at most 60 seconds; 15, then 30, then 60 seconds when there is none) and asks again from the same address, as often as Retries per request allows. It does not ask again when the service says that no request is left (x-ratelimit-remaining: 0), or when it asks for a wait of more than a minute. A refused request is never asked for again from another address, and a proxy is never used to get around a refusal or a limit.
  • The Actor stops asking a service after three requests in a row that failed for good, or five failed attempts in a row. Results saved before that are kept and the entries that were not read are marked blocked or stopped in the run summary. A run that saved nothing ends as failed with a clear message, and nothing is charged for results. Wait a few minutes and run again.
  • The UK dutyRate is the third-country rate: it applies to goods from any country without a trade preference. Preferential rates, quotas, suspensions, anti-dumping duties and other additional duties and trade measures are not in the row. For some lines the source shows no single rate for all goods: the rate depends on an additional code (for example a relief for listed pharmaceutical substances next to the rate for everything else), or only a duty under authorised use is published. For these lines dutyRate is empty and dutyNote lists the published rates.
  • The US dutyRate is the general (column 1) rate only. The special rates (for example under free trade agreements) are not in it, and neither are the additional duties of Chapter 99: Section 301, Section 232, IEEPA-based duties and others. Those duties change often, and the text of an HTS revision can still list measures that have ended. Check Chapter 99 and the current notices, or ask your customs broker, before you quote a landed cost. Open the url of the row to read the line on the source site.
  • US rows carry the parent lines from the 4-digit heading down, including the grouping lines that have no code. The US data has no chapter or section text.
  • Text search follows the wording of the tariff, not everyday product names. Results under a grouping line come in tariff order. Some everyday words return nothing.
  • A code that does not exist gives no result: the entry is marked no-match in the run summary and the run still succeeds. A run fails only when nothing was saved because a service refused the requests or did not answer, or because entries were not read although nothing stopped the run.
  • When a run stops early (result limit, maximum cost, time limit, or a service that refuses), the final message says why and the run summary marks the entries that were not read as stopped or blocked.
  • A run can end before every entry was read, although no limit was reached. In another Actor of this studio, the part that reads the pages was seen to stop while a request was still waiting. That happened when several runs were started at the same moment. It was not seen in HS Code Lookup API, and the cause is not known. The entries are read one after the other, so the entries behind a request that is never read are not read either. HS Code Lookup API checks for this when the reading ends. Without a proxy, which is the default, it starts reading again, at most three times, with a longer wait each time, and writes a run.pending line to the log. With a proxy it does not start again: a new start opens a new proxy session, which can mean another address, and HS Code Lookup API does not move a run to another address by itself. It then only reports what is left. The run summary counts the new starts and the requests that were still waiting under totals (extraPasses and requestsLeft). An entry that is still not read after that is marked not-read, and nothing is charged for it.
  • Results are de-duplicated within one entry and tariff, not across entries or runs. The same code found by two entries is saved once for each.
  • Tariffs change. Each row has a retrievedAt time; run again when you need current rates.

FAQ

Which data does HS Code Lookup API collect, and which not?

It reads public tariff data only: codes, official descriptions, parent lines, duty and VAT rates and units. It does not log in and does not use an API key. It does not collect personal data: no contact persons, phone numbers or email addresses. It does not collect preferential rates, quotas, anti-dumping measures, licence requirements, footnotes, customs rulings or trade statistics.

Is the result a binding classification?

No. It is a starting point. Only the customs authority can give a binding ruling, and the importer stays responsible for the code on the declaration.

Can I use the duty rate for another country?

No. The duty is the UK rate or the US rate only. The 6-digit hs6 code is the part you can take to another country and look up in that national tariff.

Is dutyRate the full duty I pay at the border?

No. It is the basic rate of the tariff line: for the UK the third-country rate, for the US the general (column 1) rate. Preferences, quotas and anti-dumping duties are not in it, and for the US neither are the additional duties of Chapter 99 (Section 301, Section 232, IEEPA-based duties and others). Those duties change often, and the text of an HTS revision can still list measures that have ended. Use dutyRate as the starting point of a landed-cost sheet and confirm the full duty with your customs broker or the customs authority.

Why did my run return 0 results?

Either no tariff line matched your entries (for the US, no line that contains every word), or a code does not exist, or the source refused the requests, or the run ended before it read the entries. The run summary in the Storage tab shows the status of every entry, a note for entries without a result, and each failed request. An entry that the run did not read to the end has the status not-read. The status message of the run says how many entries were not read, and the run summary lists them. Nothing is charged for results in any of these cases.

Why is dutyRatePercent empty when dutyRate has a value?

The number is filled only when the rate is a plain percentage, and "Free" counts as 0. Rates by weight or quantity and mixed rates stay as text in dutyRate.

Why is dutyRate empty for a line that has a duty?

A duty rate that is wrong is worse than none. When the UK tariff shows different rates for the same code, depending on an additional code, the Actor cannot know which one applies to your goods. The same is true when only a duty under authorised use is published. In both cases dutyRate and dutyRatePercent are empty and dutyNote lists the rates, for example "The duty depends on the additional code declared: 6.00 % (2501 Other); 0.00 % (2500)". Open the url of the row to read the conditions.

Can I schedule HS Code Lookup API?

Yes. Save your input as a task and add a schedule in Apify Console, for example a monthly check of the codes you import.

Feedback

Tell us which tariff, country or field to add next in the Feedback for the developer input field, or open an issue in the Issues tab. Feedback is saved with the run summary.