# HS Code Lookup API (`sourcing-data-studio/hs-code-lookup-api`) Actor

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.

- **URL**: https://apify.com/sourcing-data-studio/hs-code-lookup-api.md
- **Developed by:** [Sourcing Data Studio](https://apify.com/sourcing-data-studio) (community)
- **Categories:** E-commerce, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 tariff results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

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.

```json
[
  {
    "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?

| Field | Type | Description |
|---|---|---|
| `searchQuery` | string | The entry from your input that produced this row: a product description or a code. |
| `tariff` | string | The national tariff the row comes from: UK (UK Trade Tariff) or US (US Harmonized Tariff Schedule). |
| `code` | string | National 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). |
| `hs6` | string | First 6 digits of the code, the part shared internationally. Null for rows at heading level, which have no 6-digit code. |
| `description` | string | Official description of the tariff line, as published. |
| `hierarchy` | array | Descriptions 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. |
| `dutyRate` | string | Import 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). |
| `dutyNote` | string | Filled 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. |
| `dutyRatePercent` | number | The 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. |
| `vatRate` | string | UK import VAT as published (for example 20.00 %). Always null for the US. |
| `units` | string | Supplementary unit or unit of quantity as published (for example p/st for the UK, No. for the US). Null when none is shown. |
| `isDeclarable` | boolean | True 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. |
| `matchType` | string | How the row was found: code when your entry was a code, text when it was a product description. |
| `url` | string | Public page for the code on the source site. |
| `source` | string | Source site domain: trade-tariff.service.gov.uk or hts.usitc.gov. |
| `retrievedAt` | string | ISO 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](https://www.trade-tariff.service.gov.uk/) under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/). 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](https://hts.usitc.gov/).

### 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

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

#### Two tariffs by code

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

#### Compare candidate codes

```json
{
  "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?

| Event | You pay for | Free | Bronze | Silver | Gold |
|---|---|---|---|---|---|
| Tariff result | One 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**

```bash
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`)

```javascript
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)

```python
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.

# Changelog

This Actor's version history is a separate document: https://apify.com/sourcing-data-studio/hs-code-lookup-api/changelog.md

# Actor input Schema

## `searchQueries` (type: `array`):

One entry per lookup. Each entry is either a short product description in English (for example: phone case, usb cable, laptop) or a tariff code of 4, 6, 8 or 10 digits (for example: 8517, 851713, 85171300 or 8517130000; dots are allowed, as in 8517.13.00). A code is looked up directly. A description is searched in the tariff, and the best matching codes are returned; in the US tariff only lines that contain every word of the description count as a match. Use plain product words, not brand names or model numbers. Write codes as text, so that a leading zero is kept. At most 100 entries per run.

## `tariffs` (type: `array`):

Which national tariff to read. UK is the UK Trade Tariff (10-digit commodity codes, UK third-country duty and UK import VAT). US is the US Harmonized Tariff Schedule (10-digit HTS codes, US general duty rate). Choose both to get one set of results per tariff for every entry. The first 6 digits of a code are the same in every country; the remaining digits and the duty rate are national.

## `maxResultsPerQuery` (type: `integer`):

How many tariff results to save for each entry and each tariff, from 1 to 20. A declarable 10-digit code gives one result; a 10-digit grouping code also returns the lines below it. A shorter code or a product description can match several tariff lines; the best matches are saved first. Use 1 when you only want the top match, and 5 or more when you want to compare candidate codes.

## `maxItems` (type: `integer`):

Maximum number of tariff results to save and pay for in this run, across all entries and tariffs. The run stops as soon as this many results are saved.

## `proxyConfiguration` (type: `object`):

Proxy servers used for the requests. Both sources are open government services that answer direct requests, so no proxy is needed and the default is no proxy. Change this only if your network cannot reach the sources directly. A proxy is never used to get around a refusal: a request that a source refused is not asked for again from another address.

## `maxRequestRetries` (type: `integer`):

How many times a failed request is asked for again, with a growing wait between attempts. A request that a source refuses with HTTP 401, 403 or 407 is not asked for again. After HTTP 429 (too many requests) the Actor waits as long as the source asks and asks again from the same address, unless the source says that no request is left. When a source refuses or fails several requests in a row, the run stops asking that source, whatever this setting. Failed requests are never charged.

## `debugLog` (type: `boolean`):

Print detailed debug messages to the run log.

## `feedback` (type: `string`):

Optional. Tell us which field, tariff or country you need next, or what went wrong. It is saved with the run summary. Do not include personal data. AI agents may also use this field to report problems.

## Actor input object example

```json
{
  "searchQueries": [
    "8517130000",
    "phone case",
    "usb cable",
    "laptop"
  ],
  "tariffs": [
    "UK",
    "US"
  ],
  "maxResultsPerQuery": 2,
  "maxItems": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxRequestRetries": 3,
  "debugLog": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "8517130000",
        "phone case",
        "usb cable",
        "laptop"
    ],
    "tariffs": [
        "UK",
        "US"
    ],
    "maxResultsPerQuery": 2,
    "maxItems": 10,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("sourcing-data-studio/hs-code-lookup-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQueries": [
        "8517130000",
        "phone case",
        "usb cable",
        "laptop",
    ],
    "tariffs": [
        "UK",
        "US",
    ],
    "maxResultsPerQuery": 2,
    "maxItems": 10,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("sourcing-data-studio/hs-code-lookup-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "8517130000",
    "phone case",
    "usb cable",
    "laptop"
  ],
  "tariffs": [
    "UK",
    "US"
  ],
  "maxResultsPerQuery": 2,
  "maxItems": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call sourcing-data-studio/hs-code-lookup-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sourcing-data-studio/hs-code-lookup-api"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/OOzy8zUbpI1DrEUwu/builds/agGknLa3VMzLqN3uy/openapi.json
