# UK Operator Licence Register: Goods & PSV, Weekly Changes (`solidfetch/uk-operator-licences`) Actor

UK goods and bus/coach operator licences from the official DVSA and NI registers (OGL): licence number, operator, type, status, traffic area, vehicles authorised, operating centre. Search, look up, or get new and changed licences each week. No personal data by default. Pay per licence.

- **URL**: https://apify.com/solidfetch/uk-operator-licences.md
- **Developed by:** [Solid Fetch](https://apify.com/solidfetch) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 licences

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

## UK Operator Licences: HGV and PSV O-Licence Register with Weekly Changes

Get UK goods vehicle (HGV) and bus and coach (PSV) operator licences from the official Traffic Commissioner register, as an API or a scraper alternative. Look up a licence number, search by traffic area, licence type, status, fleet size or operator name, or get the weekly feed of new, changed and removed licences. Each licence is one row with the licence number, operator legal name and trading names, licence type and status, traffic area, vehicles and trailers authorised, operating centres, company number, continuation date and the list of changes. The data is the register DVSA publishes every week on data.gov.uk for Great Britain, plus the Northern Ireland register, both under the Open Government Licence. The Northern Ireland file is old (data as of 8 January 2023) and every Northern Ireland row says so in `sourceDate`. No login, no proxy and no scraping of the VOL website.

The actor downloads the official weekly CSV files. There is no login, no browser, no proxy and no scraping of the "Check a vehicle operator licence" service.

### What people use it for

- **New hauliers and fleet changes, every week.** A new operator licence is the moment a haulier buys fuel cards, telematics, insurance, finance and trailers. Schedule `changesSince: "7 days"` every Monday and get the licences that are new since last week's file, the ones whose authorised fleet grew or shrank, the ones curtailed or suspended, and the ones that dropped out of the register. Each row says what changed, from what to what, and in which weekly file.
- **Market lists.** All standard international licences in the North West with 10 to 50 vehicles, restricted licences in Scotland, every PSV operator in Wales. Filters run inside the actor, so you pay only for licences that match.
- **Vetting.** Paste licence numbers and get type, status, fleet size and continuation date for each. Numbers that are not in the register, and values that are not licence numbers at all, get a free row each and are listed in the run summary.
- **CRM enrichment.** Add the licence, fleet size and Companies House number to accounts you already hold.

### How to use it

The weekly changes feed:

```json
{
    "changesSince": "7 days",
    "trafficAreas": ["northWest", "westMidlands"],
    "maxItems": 5000
}
```

A market list:

```json
{
    "trafficAreas": ["northWest"],
    "licenceTypes": ["standardInternational"],
    "statuses": ["valid"],
    "minVehicles": 10,
    "maxVehicles": 50,
    "maxItems": 1000
}
```

A lookup:

```json
{
    "licenceNumbers": ["OB1234567", "pk 0123456"]
}
```

A lookup cannot be combined with search filters, because the filters would be ignored. A field name the actor does not know stops the run with a hint such as `"status" (did you mean "statuses"?)`, so a typo cannot turn into a run of unfiltered licences.

### Input

| Field | What it does |
|---|---|
| `changesSince` | Only licences that are new, changed or removed in a weekly file of this date or later. A date (`2026-10-19`) or a period back from today (`7 days`, `2 weeks`, `1 month`). See "How the changes feed works". |
| `trafficAreas` | `northEast`, `northWest`, `westMidlands`, `east`, `wales`, `westOfEngland`, `londonSouthEast`, `scotland`, `northernIreland`. Area names such as "Wales" or "London and the South East of England" work too. Empty means all nine. |
| `licenceCategory` | `goods` (default, licence numbers starting with O), `psv` (starting with P) or `any`. |
| `licenceTypes` | `standardNational`, `standardInternational`, `restricted`, `specialRestricted` (PSV). |
| `statuses` | `valid`, `curtailed`, `suspended`, `revoked`, `surrendered`. Empty means every status in the file. The Great Britain files hold only `valid`, `curtailed` and `suspended` licences (about 99% are valid); a revoked or surrendered licence is not in them and shows as `removed` in the changes feed. The Northern Ireland file has no status, so a status filter leaves Northern Ireland out. |
| `minVehicles`, `maxVehicles` | Range of vehicles authorised. Licences without a number are dropped when you set a range. |
| `operatorName` | Words that must all start a word of the legal or trading name. `smith haul` finds SMITH HAULAGE LTD. |
| `licenceNumbers` | Licence numbers to look up, up to 10,000 per run, returned in the order given. Spaces, dashes and lower case are fine. The second letter is the traffic area, and the actor reads that area's file first; a licence that moved area is found in the others. |
| `includeSoleTraderContacts` | Default `false`. See Personal data below. |
| `maxItems` | Stop after this many licences, up to 1,000,000. Left empty, a search or changes run stops at 100 and a lookup returns every licence it finds. |
| `historyStoreName` | Default `uk-operator-licences-history`. The key-value store on your account that holds the history for the changes feed. Use another name to keep a separate history. |

### Output

One row per licence. An example built from the actor's test data (invented operator, real row shape), from a changes run:

```json
{
    "licenceNumber": "OG1000001",
    "listed": true,
    "licenceCategory": "goods",
    "licenceType": "standard international",
    "status": "curtailed",
    "trafficArea": "Wales",
    "trafficAreaKey": "wales",
    "operatorName": "EXAMPLE HAULAGE 1 LTD",
    "tradingNames": ["QUICKMOVE"],
    "holderType": "limited company",
    "individualHolder": false,
    "contactsHidden": false,
    "companyNumber": "01234001",
    "companyNumberKind": "companies house",
    "correspondenceAddress": "Unit 1, Sample Industrial Estate, Sampletown, CF21 1XY",
    "correspondencePostcode": "CF21 1XY",
    "operatingCentres": [
        { "address": "Yard 1, Depot Road, Sampletown, CF22 1ZZ", "postcode": "CF22 1ZZ", "postcodeDistrict": "CF22" }
    ],
    "operatingCentreCount": 1,
    "vehiclesAuthorised": 6,
    "trailersAuthorised": 1,
    "vehiclesSpecified": 1,
    "trailersSpecified": 0,
    "transportManagerCount": 2,
    "directorOrPartnerCount": 1,
    "continuationDate": "2029-02-02",
    "firstSeen": null,
    "lastChanged": "2026-10-25",
    "changes": [
        { "date": "2026-10-25", "kind": "changed", "field": "status", "from": "valid", "to": "curtailed" },
        { "date": "2026-10-25", "kind": "changed", "field": "vehiclesAuthorised", "from": 2, "to": 6 }
    ],
    "companiesHouseUrl": "https://find-and-update.company-information.service.gov.uk/company/01234001",
    "sourceDate": "2026-10-25",
    "source": "Traffic Commissioners: goods and public service vehicle operator licence records (DVSA), https://www.data.gov.uk/dataset/traffic-commissioners-goods-and-public-service-vehicle-operator-licence-records, file \"Wales\", Open Government Licence v3.0",
    "fetchedAt": "2026-10-26T08:00:00.000Z"
}
```

| Field | Meaning |
|---|---|
| `licenceNumber`, `licenceCategory` | The operator licence number, and `goods` or `psv` from its first letter. |
| `licenceType`, `status` | `standard national`, `standard international`, `restricted`, `special restricted`; `valid`, `curtailed` or `suspended` (the register writes them as `lsts_valid` and so on; any other status code is passed through in lower case and counted in the run summary). `status` is `null` for Northern Ireland. |
| `trafficArea`, `trafficAreaKey` | The Traffic Commissioner area whose file the licence is in. |
| `operatorName`, `tradingNames` | The licence holder's legal name, and the names after "T/A" or "trading as". |
| `holderType`, `individualHolder`, `contactsHidden` | `limited company`, `llp`, `sole trader`, `partnership`, `public body` or `other`; whether the holder is one or more private persons; whether addresses were left out for that reason. |
| `companyNumber`, `companyNumberKind`, `companiesHouseUrl` | The company number from the register (GB files only; `null` for most sole traders and partnerships). `companyNumberKind` is `companies house` for numbers in Companies House's eight-character form (padded with zeros, e.g. `01234001`, `SC012345`), with `companiesHouseUrl` linking to the company's page, or `registered society` for the numbers of co-operatives and other societies, kept as the register writes them (`IP22866R`, `SP1369RS`, `2353RS`), with no link. 41 of the 69,027 GB licences had one in October 2026. |
| `correspondenceAddress`, `correspondencePostcode` | The address the licence holder gave for letters. |
| `operatingCentres`, `operatingCentreCount` | The operating centre (where the vehicles are kept) that the register file gives for the licence, with its postcode and postcode district. The files carry one operating centre address per licence, so this is a list of one (empty when the file has none): a licence with several operating centres shows one of them, and `operatingCentreCount` is 1, not the number of centres the licence has. |
| `vehiclesAuthorised`, `trailersAuthorised`, `vehiclesSpecified`, `trailersSpecified` | How many vehicles and trailers the licence allows, and how many are specified on it. |
| `transportManagerCount`, `directorOrPartnerCount` | How many transport managers and directors or partners the register lists. Their names are never returned. |
| `continuationDate` | When the licence is next due for continuation (every five years). It is not an expiry date. |
| `listed` | `false` for a licence that has dropped out of the register file since an earlier run; the row then holds the last values seen and no addresses. |
| `firstSeen`, `lastChanged`, `changes` | From the history the actor keeps (see below). `firstSeen` is the weekly file in which a licence first appeared, `null` when it was already there when the history started. `changes` lists `new`, `changed` (with `field`, `from` and `to`), `removed` and `relisted`, each with the `date` of the weekly file. With `changesSince`, only the changes since that date; otherwise the last 20. |
| `sourceDate`, `source` | The date of the file the row comes from, and the dataset and licence it was published under. For Great Britain this is the date the weekly file was published (its `Last-Modified`). For Northern Ireland it is the date in the file's name, which is when the data was extracted: 2023-01-08 at the time of writing, and the `source` text says "data as of" that date. The run also warns in its log and summary when the Northern Ireland file is older than 60 days. |

A lookup also saves a free row for every value that gave no licence: `outcome` is `invalid` (not a licence number) or `not found` (not in the register files), `query` is the value and `error` says why. These rows are never charged and do not count against `maxItems`. To keep only licences in an export, filter on `outcome` being empty.

The run also saves a `SUMMARY` record in the key-value store: the files read with their dates and how many licences were new, changed and removed in each, the files that could not be read, the numbers not found, the rows with addresses hidden, and any values the actor could not read.

### How the changes feed works

The register files are a snapshot. They say which licences exist this week, not when one was granted or changed. The actor therefore keeps its own history: a record of every licence it has seen, per traffic area, in a key-value store on your Apify account (`historyStoreName`). Each run compares the current weekly file with that record and dates every difference with the file's date.

- **The first run records the starting point and returns no changes.** Its status message says so, and it costs only the start of the run. From the following Sunday's file on, `changesSince` returns what changed.
- Running twice in one week returns the same changes, because both runs see the same file.
- Any run, including a search or a lookup, updates the history of the areas it reads. A run limited to some `trafficAreas` leaves the other areas' history as it was, and their next run reports everything since then under the date of that file.
- The fields compared are status, licence type, vehicles and trailers authorised, number of operating centres, name, company number, continuation date and holder type. A change of address alone is not reported.
- A file that suddenly holds less than half of last week's licences is treated as a broken download and not used.

### Send results to Make, Zapier, n8n or a CRM

Long runs do not need to keep a Make or Zapier scenario waiting:

1. Start the actor or one of its saved tasks on a schedule, or from your scenario with the asynchronous "Run Actor" step. For the changes feed, a weekly schedule on Monday morning (UK time) picks up the Sunday file.
2. Trigger the next step on the finished run. In Make use the Apify module "Watch Actor Runs", in Zapier the Apify trigger for a finished actor run, in n8n the Apify node.
3. Read the rows with "Get Dataset Items" using the run's dataset ID.

For your own endpoint, open the actor's Integrations tab and add an HTTP webhook on "Run succeeded". The payload contains `resource.defaultDatasetId`, which you can read at `https://api.apify.com/v2/datasets/<datasetId>/items?format=json`. Add `&format=csv` for a spreadsheet, or `&view=changes` for the changes columns.

### Pricing

You pay per licence saved (event `licence`), and the price is on the Pricing tab. Numbers that are not found and values that are not licence numbers (both come back as free rows), the first changes run that only records the starting point, and rows past your cost limit are not charged. When a run reaches the maximum cost you set, it stops and keeps what was saved, and it never saves a licence the remaining budget cannot pay for. If the platform migrates or restarts a run, the run picks up its own record of what it saved (`RUN_STATE` in the key-value store) and does not save or charge those licences, or the free rows, again.

### Source and licence

- Great Britain: "Traffic Commissioners: goods and public service vehicle operator licence records", published by the Driver and Vehicle Standards Agency from the Vehicle Operator Licensing (VOL) database: https://www.data.gov.uk/dataset/traffic-commissioners-goods-and-public-service-vehicle-operator-licence-records. One CSV per traffic area, refreshed weekly (the publisher says on Sundays; the files recorded on 21 October 2026 were dated Wednesday 7 October 2026, so read `sourceDate` rather than assuming a day).
- Northern Ireland: "Northern Ireland goods vehicle operator's licence records", Department for Infrastructure: https://www.data.gov.uk/dataset/northern-ireland-goods-vehicle-operator-s-licence-records. This file is not refreshed every week: the copy on data.gov.uk is dated 8 January 2023 (it was copied to the server again on 1 January 2024, which the `Last-Modified` header shows, but the data is the same), so treat Northern Ireland as a snapshot from January 2023. Every Northern Ireland row carries that date in `sourceDate`. The file has no status, continuation date or company number columns (its header has 12 columns, the GB header 16), so those are `null` for every Northern Ireland row.
- The actor finds the files through the data.gov.uk catalogue API on every run, so a renamed file does not break it.
- The published files are "a similar version" of the internal VOL database (Department for Transport, Statement of Administrative Sources), not the database itself, and it is refreshed once a week, so a licence granted on a Monday shows up in the file of the following Sunday at the earliest.
- Licence: both datasets are published under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/), which allows commercial reuse with attribution. Attribution for your own use of the data: "Contains public sector information licensed under the Open Government Licence v3.0." Every row carries a `source` field that names its dataset.

### Personal data

Many operator licences are held by sole traders and partnerships. Their name is on the public register, but their correspondence address and operating centre are often their home. The register says for each licence whether it is held by a company, an LLP, a sole trader or a partnership; where it does not, the actor reads the name, and a name without a legal-form word such as Ltd, Limited, PLC or LLP is treated as a person. For licences held by persons the actor leaves out both addresses and the full postcodes by default and keeps only the postcode district (for example CF10), which places the operating centre without naming a house.

The register also names transport managers and directors or partners. The actor never returns these names, with no option to turn them on: it returns only how many there are. The Open Government Licence does not cover personal data.

Set `includeSoleTraderContacts` to `true` only if you have a lawful basis for processing personal data. Under the UK GDPR you are the one who decides how the data is used and who must meet its rules. For calls and e-mails to businesses, PECR applies to you as the sender, and sole traders and partnerships count as individuals there. The actor provides public register data and does not give legal advice.

When a run fails, an error report with the error message and the shape of the input (which fields are set, how long the lists are), never your input values or your results, is sent to the developer to fix bugs.

### Limits

- The changes feed needs two weekly files: the first run only records the starting point.
- Licences granted and removed within the same week never appear in a weekly file, so the feed cannot show them.
- Status filters drop Northern Ireland licences, because the Northern Ireland file has no status.
- The Northern Ireland file is from January 2023 (see `sourceDate`); do not use it for the current state of the register.
- The Great Britain files list only licences in force (valid, curtailed or suspended). A revoked or surrendered licence shows as `removed` in the feed rather than with a revoked status.
- The files give one operating centre address per licence, so `operatingCentres` never lists more than one.
- A full run over all areas downloads nine files of several megabytes each. The actor reads them one at a time with a pause between downloads, and stops downloading if three files fail in a row; files it could not read are named in the status message and the summary, and if none could be read the run fails without charging anything.
- The actor stops with an error if a file changes its columns or a column suddenly holds values it cannot read, before anything from that file is saved.

If this actor saved you time, a short review on the actor page helps other people find it.

# Actor input Schema

## `changesSince` (type: `string`):

Only licences that are new, changed (status, licence type, vehicles or trailers authorised, operating centres, name, company number) or removed in a weekly register file of this date or later, each with its list of changes. The register has no change dates, so the actor compares each weekly file with the history it keeps in a named key-value store on your account: the first run records the starting point and returns nothing, changes show from the next weekly file. Schedule it weekly, for example on Mondays.

## `trafficAreas` (type: `array`):

Traffic Commissioner areas to read. Empty means all nine (eight in Great Britain, plus Northern Ireland).

## `licenceCategory` (type: `string`):

Goods vehicle licences (numbers starting with O), PSV licences (starting with P), or both.

## `licenceTypes` (type: `array`):

Empty means all. Standard licences carry goods or passengers for hire or reward; a restricted goods licence carries only the operator's own goods.

## `statuses` (type: `array`):

Empty means every status in the file. The Northern Ireland file has no status, so a status filter leaves Northern Ireland out.

## `minVehicles` (type: `integer`):

Fleet size: the number of vehicles the licence authorises.

## `maxVehicles` (type: `integer`):

Upper end of the fleet size range.

## `operatorName` (type: `string`):

Words that must all start a word of the operator's legal or trading name, without regard to case. "smith haul" finds SMITH HAULAGE LTD.

## `licenceNumbers` (type: `array`):

Look up these licences instead of searching, for example OB1234567 or PK0123456. Up to 10,000 per run, returned in the same order. A value that is not a licence number, or a number not in the register, gets a free row.

## `includeSoleTraderContacts` (type: `boolean`):

Licences held by private persons (sole traders and partnerships) come without correspondence and operating centre addresses by default, only the postcode district, because those are often home addresses. Turn this on only if you have a lawful basis to process personal data (see Personal data in the README). Names of transport managers and directors are never returned.

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

Stop after this many licences. You pay per licence saved. Empty means 100 for a search or changes run and every licence found for a lookup.

## `historyStoreName` (type: `string`):

Name of the key-value store on your account where the actor keeps the licence history for the changes feed. Use a different name to keep a separate history, for example per client.

## Actor input object example

```json
{
  "trafficAreas": [
    "northWest"
  ],
  "licenceCategory": "goods",
  "includeSoleTraderContacts": false,
  "historyStoreName": "uk-operator-licences-history"
}
```

# Actor output Schema

## `licences` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `addresses` (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 = {
    "trafficAreas": [
        "northWest"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidfetch/uk-operator-licences").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 = { "trafficAreas": ["northWest"] }

# Run the Actor and wait for it to finish
run = client.actor("solidfetch/uk-operator-licences").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 '{
  "trafficAreas": [
    "northWest"
  ]
}' |
apify call solidfetch/uk-operator-licences --silent --output-dataset

```

## MCP server setup

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

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/FHP5VQSzka6uRmdyC/builds/hcKEqgdDSWZTbwqD0/openapi.json
