# UK Importers and Exporters: HMRC Trade Data (`yappman/uk-importers-exporters`) Actor

Find UK businesses that imported or exported goods by HS code and month, look companies up, and get alerts on traders new to a commodity. HMRC UK Trade Info data under the OGL. Names that are not clearly an organisation's are left out. Not affiliated with HMRC.

- **URL**: https://apify.com/yappman/uk-importers-exporters.md
- **Developed by:** [Russell Yapp](https://apify.com/yappman) (community)
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 traders

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## UK Importers and Exporters: HMRC Trade Data

Not affiliated with HMRC.

This Actor returns one row per trader, flow (imports or exports) and month, with the commodity codes that trader dealt in. The data comes from HMRC UK Trade Info, the monthly lists of UK importers and exporters published by HMRC, published under the Open Government Licence v3.0. Names that are not clearly an organisation's are left out and counted, because sole traders are people.

**What a first run costs.** The input is prefilled: search HS code `8419` in the latest month HMRC has published, `maxItems` 20. That costs at most 20 × $0.002 + $0.005 = **$0.045**. The prefilled run reads the latest month's bulk files (importers and exporters) and makes no API call.

**To get more rows,** raise `maxItems` (Maximum rows) in the input. It goes up to 100,000. `maxItems` caps the trader rows returned. Each row costs $0.002, and the $0.005 run fee is charged on top once the run has read data, so a run costs at most `maxItems` × $0.002 + $0.005.

### What you get

One row per trader, flow and month. A missing value is `null` (or `[]` for lists), never an absent key.

An illustrative row (the name, key and district are invented, not a real trader's):

```json
{
  "traderId": null,
  "traderKey": "0a1b2c3d4e5f6071",
  "name": "Example Trading Limited",
  "postcodeDistrict": "ZZ1",
  "flow": "imports",
  "month": "2026-07",
  "cn8Codes": [
    "84198100",
    "84199085"
  ],
  "hs6Codes": [
    "841981",
    "841990"
  ],
  "hs4Codes": [
    "8419"
  ],
  "otherCodes": [],
  "source": "bulk",
  "publishedAt": "2026-09-07T11:14:43Z",
  "matchType": "search",
  "alertFingerprint": null
}
```

### Modes

Set `mode` to `search` (the default), `lookup` or `alert`. `flows` is `["imports", "exports"]` by default; give one of them to read only that flow.

#### Search

The traders that dealt in some HS codes in a month or a range of months.

```json
{
  "mode": "search",
  "hsCodes": ["8419"],
  "flows": ["imports", "exports"],
  "monthFrom": "2026-01",
  "monthTo": "2026-06",
  "maxItems": 200
}
```

- `hsCodes`: 1 to 20 codes of 2, 4, 6 or 8 digits (chapter, heading, subheading or CN8). A code matches every 8-digit code that starts with it.
- `monthFrom` and `monthTo` (`YYYY-MM`): give both or neither. With neither, the search reads the latest month HMRC has published.
- The latest published month is read from HMRC's bulk file. Older months are read from the HMRC API.
- Each row lists only the codes that matched.

#### Lookup

What named companies imported or exported.

```json
{
  "mode": "lookup",
  "companies": ["Example Trading Limited", "123456"],
  "flows": ["imports"],
  "monthFrom": "2026-01",
  "monthTo": "2026-06",
  "maxItems": 100
}
```

- `companies`: 1 to 20 entries. Each is a company name, or part of one (at least 3 characters), or an HMRC trader id (digits only).
- A name matches any published name that contains it. Up to 10 traders are taken per name; if there are more, the count is in `RUN_SUMMARY.namesWithMoreMatches`.
- Without `monthFrom` and `monthTo`, lookup covers the 12 months to the latest published month.
- Lookup reads the HMRC API. Each row lists every code the trader dealt in that month.
- Lookup does not match a name that fails the organisation test. Names that found nothing are counted in `RUN_SUMMARY.companiesNotFound`.

#### Alert

Traders that are new to your HS codes each time HMRC publishes a month.

```json
{
  "mode": "alert",
  "alertId": "heat-exchangers",
  "hsCodes": ["8419"],
  "flows": ["imports"],
  "lookbackMonths": 12
}
```

- `alertId` is yours to choose: 1 to 40 characters of lower-case letters, digits and hyphens, starting and ending with a letter or digit.
- An alert's `hsCodes` are fixed. A run with other codes for the same `alertId` is refused before anything is read; use a new `alertId`.
- `lookbackMonths` (1 to 24, default 12): a trader is new when it did not trade your codes in that many months before.
- `monthFrom` and `monthTo` are refused in alert mode: an alert reads each new month as it is published.
- State is kept in a key-value store named `uk-traders-alert-<alertId>` in your own Apify account. It holds hashes, not names.
- **The first run reads the latest month, records its traders and sends no rows.** New traders are sent from the next month on. The first run is charged the `run` event, because it read data. The same applies to a flow you add to an alert later, and to an alert that has not run for longer than `lookbackMonths`: that month is recorded, not sent.
- Later runs read every month published since the last run (up to `lookbackMonths`) and send the traders not seen in the `lookbackMonths` before. A trader is sent once per `alertId`, and again only if it comes back after that long.
- A run with no new month reads no trader data and costs nothing. You can schedule an alert daily.
- If `maxItems` or your spending limit cuts a run short, the unsent traders are sent on the next run. In alert mode `maxItems` is at most 5,000.
- An alert remembers up to 20,000 traders. If your codes match more than that in the first month read, the run stops with `tooBroad`, saves nothing and charges nothing: use narrower codes. Later months forget the oldest traders first, and the status says how many.
- A second run for the same `alertId` that starts while one is active stops before reading anything.

### Output fields

| Field | Type | Meaning |
| --- | --- | --- |
| `traderId` | integer or null | HMRC's trader id on rows read from the API. `null` on rows read from the bulk file, which has no id |
| `traderKey` | string | A 16-character hash of the published name and the postcode's outward code. The same trader has the same key in the bulk file and the API, unless its name or postcode differs between the two |
| `name` | string | The trader's name as published. Names that are not clearly an organisation's are withheld |
| `postcodeDistrict` | string or null | The outward code of the postcode only (for example `ZZ1`), never the full postcode |
| `flow` | `"imports"` or `"exports"` | The direction of trade |
| `month` | string | The month of trade, `YYYY-MM` |
| `cn8Codes` | array of strings | 8-digit commodity codes. In search and alert, only the codes that matched your `hsCodes` |
| `hs6Codes` | array of strings | The 6-digit codes of `cn8Codes` |
| `hs4Codes` | array of strings | The 4-digit headings of `cn8Codes` |
| `otherCodes` | array of strings | Two-digit chapter codes (such as `99`) that the bulk file lists for the trader |
| `source` | `"bulk"` or `"api"` | Where the row was read from |
| `publishedAt` | string or null | The bulk file's last-modified time, ISO 8601 UTC. `null` on API rows |
| `matchType` | `"lookup"`, `"search"` or `"alert"` | The mode that returned the row |
| `alertFingerprint` | string or null | Set in alert mode only; `null` otherwise |

There is no address field.

### Pricing

Pay per event. Both events are flat prices.

| Event | Charged | Price |
| --- | --- | --- |
| `run` | Once per run that read trader data | $0.005 |
| `trader` | Each row returned | $0.002 |

Examples: a 20-row search costs $0.045. A 1,000-row search costs $2.005. A monthly alert that finds 30 new traders costs $0.065.

**The `run` event is charged** once a run has read trader data: a bulk file or an answer from the HMRC API. It is charged even if the run returns no rows.

**The `run` event is not charged, and nothing else is, when:**

- the input is refused (the run ends as succeeded, with the reason as its status message and `RUN_SUMMARY.status` `refused`, and nothing is read);
- the pricing is not set as this Actor expects (it needs a flat price for both events, and no per-item dataset price), so the run refuses to start and fails;
- an alert's saved memory was written by a different version of the Actor (the run fails, and nothing is read; use a new `alertId`);
- an alert finds another run of the same `alertId` active (`locked`), or the `alertId` was set up for other `hsCodes`;
- an alert's codes match too many traders to remember (`tooBroad`);
- an alert finds no new month since its last run (`noNewMonth`). Checking for a new month reads only HMRC's bulk-data page, which is not trader data;
- an alert loses its lock part-way (`lockLost`): it sends and charges nothing.

The first run of an alert reads the month and is charged `run`, but sends no rows.

A run can also end with an `incomplete` note after `run` was charged: one flow or month was read and another could not be (for example the exports file failed while imports parsed). The run is not marked failed, because data was read; `incomplete` in `RUN_SUMMARY` says what was missed. A run that read nothing because every part failed is marked failed, and charges nothing.

Rows are charged only as they are stored. If you set a maximum charge on the run and it is reached, the run stops, and `RUN_SUMMARY.chargeLimitReached` is `true`.

### Personal data

Sole traders are people, and HMRC publishes their names next to companies'. This Actor does not return them.

- **Names.** A name is returned only if it carries an organisation marker, such as Limited, Ltd, PLC, LLP, a foreign company form, or a public-body word. A name without one is withheld, and so is a name with a title (Mr, Mrs, Dr) or a trading-as name unless a legal form follows the title or comes before the trading-as name. A name that holds a contact or an address (a "C/O" or "FAO" contact, a postcode, or other words after the company's legal form) is withheld too. In search and alert it is left out and counted in `RUN_SUMMARY.withheldPersons`. Lookup never matches it: a company input that matches only such names is reported exactly as one that matches nothing.
- **Addresses.** Address lines are never output or stored. HMRC's bulk files carry five address columns, which the Actor reads past and ignores; name and id lookups ask HMRC's API for the trader's id, name and postcode only, so they receive no address lines. Only the outward code of the postcode is output.
- **HMRC's removals.** HMRC removes a trader who asks, from the following month's publication. It does not apply this to months already published. Every run reads HMRC's current publication, and no trader name or address is kept between runs. The alert store holds only hashes. So once HMRC stops publishing a trader, this Actor stops returning it, from the month after the request.
- **Asking us to remove a trader.** Email info@yappman.com with the trader's name as it appears in the results (or its HMRC trader id). We reply to removal requests within one month, and each accepted request is a reviewed change to our removal list. We keep a removal list of our own, alongside HMRC's. Traders on it are never returned and are counted in `RUN_SUMMARY.removedByRequest`.
- The Open Government Licence does not cover personal data, so sole traders' details are not ours to republish.

### Limits

- HS codes: 1 to 20 per run, of 2, 4, 6 or 8 digits.
- Companies: 1 to 20 per run (lookup).
- Months: at most 12 per run, from 2016-01. `monthFrom` and `monthTo` are given together.
- Freshness: the latest month is about 5 weeks behind. For example, July 2026 was published on 7 September 2026. A month that is not yet published is listed in `RUN_SUMMARY.notPublished`.
- API queries: a search makes at most 60 (older months × HS codes × flows). A wider search is refused before anything is read, and nothing is charged. Split the months or the codes across runs.
- API use: each run is limited to 200 requests and 15 minutes. Requests are paced at one every 1.5 seconds, inside HMRC's limit of 60 a minute. Each API query reads at most 20 pages of 1,000 items; a query that hits the cap is listed in `RUN_SUMMARY.incomplete`.
- Rate limit: if HMRC answers 429 or 503, the run stops using the API at once. It does not wait. Rows already read are returned, and the status message says when to retry, for example `rate-limited by HMRC: retry after 14:32 UTC`. `RUN_SUMMARY.retryAfter` has `seconds` and `until`. A 403 also stops API use and is never retried.
- `maxItems`: 1 to 100,000. The default is 1,000 when left out of an API call; the Console form prefills 20. Rows beyond it are counted in `RUN_SUMMARY.truncatedByMaxItems`.
- `lookbackMonths` (alert): 1 to 24.
- The input takes no URLs, headers or query strings: the Actor builds every request itself.

### Run summary

Every run writes a `RUN_SUMMARY` record to its key-value store. Main fields:

| Field | Meaning |
| --- | --- |
| `status` | `ok`, `refused` (input rejected; `reason` says why; the run ends as succeeded and nothing is charged; a pricing set up wrongly, or an `alertId` from a different version of the Actor, makes it fail), `failed`, or for alerts `locked`, `lockLost`, `tooBroad` or `noNewMonth` |
| `mode` | The mode that ran |
| `pushed` | Rows returned |
| `readData` | Whether the run read trader data and so owes the `run` fee. An alert that stopped as `tooBroad` or `lockLost` reports `false` even though it opened files, because it charges nothing |
| `runCharged` | Whether the `run` event was charged |
| `ownerRun` | `true` only for runs by the Actor owner's own account, which are not charged; `false` for every other run |
| `chargeLimitReached` | Whether your spending limit stopped the run |
| `latestPublished` | The latest month HMRC has published, per flow |
| `monthsRead` | The months read, per flow (lookup: `window`) |
| `notPublished` | Months you asked for that HMRC has not yet published |
| `incomplete` | Parts of the run that were not read, and why |
| `retryAfter` | If HMRC rate-limited the run: `seconds` and `until` (UTC) |
| `withheldPersons` | Traders left out because the name has no organisation marker |
| `removedByRequest` | Traders left out because they asked us to remove them |
| `matched` | Rows found before `maxItems` was applied |
| `truncatedByMaxItems` | Rows found but not returned because of `maxItems` |
| `companiesNotFound`, `namesWithMoreMatches` | Lookup only: names with no match, and names with more than 10 matches |
| `api` | API use: `requests`, `answers`, `refusals`, `stopped`, `retryAfter`, `statuses` |
| `bulkBytes` | Bytes read from HMRC's bulk site |
| `firstRun`, `seeded`, `emitted`, `candidates`, `lastMonth`, `ledgerPruned` | Alert only: whether this was the first run, the flows read for the first time (recorded, not sent), rows sent, new traders found, the last month recorded per flow, and how many of the oldest traders were forgotten because the alert's memory (1 MB) was full |
| `attribution` | The licence statement below |

### Licence and attribution

The data is HMRC UK Trade Info, published under the Open Government Licence v3.0. Please keep this attribution:

**Contains public sector information licensed under the Open Government Licence v3.0 (HMRC, UK Trade Info).**

Sources: HMRC's UK Trade Info bulk files and API only. No proxies are used.

# Actor input Schema

## `mode` (type: `string`):

search: the traders of some HS codes in a month or range; lookup: what named companies imported or exported; alert: traders new to your HS codes each month HMRC publishes.

## `hsCodes` (type: `array`):

1 to 20 codes of 2, 4, 6 or 8 digits (HS chapter, heading, subheading or CN8).

## `companies` (type: `array`):

1 to 20 company names (or part of one, at least 3 characters) or HMRC trader ids. Names that are not clearly an organisation's are never returned.

## `flows` (type: `array`):

Imports, exports or both.

## `monthFrom` (type: `string`):

YYYY-MM. With monthTo, up to 12 months from 2016-01. Search default: the latest month HMRC has published. Lookup default: the 12 months to it.

## `monthTo` (type: `string`):

YYYY-MM, with monthFrom.

## `alertId` (type: `string`):

1 to 40 characters of a-z, 0-9 and -. The alert's state lives in your own key-value store uk-traders-alert-<alertId>.

## `lookbackMonths` (type: `integer`):

A trader counts as new when it has not traded your codes for this many months.

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

Hard cap on rows returned, and so on what you are charged. Prefilled at 20; raise it for more. When left out (an API call), it is 1,000.

## Actor input object example

```json
{
  "mode": "search",
  "hsCodes": [
    "8419"
  ],
  "flows": [
    "imports",
    "exports"
  ],
  "lookbackMonths": 12,
  "maxItems": 20
}
```

# Actor output Schema

## `traders` (type: `string`):

No description

## `runSummary` (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 = {
    "hsCodes": [
        "8419"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("yappman/uk-importers-exporters").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 = {
    "hsCodes": ["8419"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("yappman/uk-importers-exporters").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 '{
  "hsCodes": [
    "8419"
  ],
  "maxItems": 20
}' |
apify call yappman/uk-importers-exporters --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yappman/uk-importers-exporters"
        }
    }
}
```

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/zkLKXXFr2UHXQ5cJD/builds/eo7vfhgZI9JlSARBO/openapi.json
