# UK Fleet & Transport Manager Leads (DVSA TC Register) (`dthen/uk-fleet-leads-dvsa`) Actor

UK fleet & transport-manager leads from the official DVSA operator-licence register. Company-level B2B data: fleet size, licence type & status, address, company reg no, renewal date. Filter by region. Weekly refresh. No personal data; clean and GDPR-friendly.

- **URL**: https://apify.com/dthen/uk-fleet-leads-dvsa.md
- **Developed by:** [Dthen](https://apify.com/dthen) (community)
- **Categories:** Automation, Developer tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## UK Fleet & Transport-Manager Leads — DVSA O-Licence Register + Companies House Enrichment

A clean, buyable B2B lead list from the **official DVSA Traffic Commissioner O-licence register** — the authoritative source of goods-vehicle operating licences in Great Britain — **enriched with Companies House company data**. Every row is a licence-level record for a company or other legal entity, ready for your fleet and transport lead generation.

### Companies House enrichment (on by default)

Each run cross-references every operator's company registration number against **Companies House** and attaches company-level data:

- **companyStatus** — active / dissolved / in administration / active — proposal to strike off / …
- **incorporatedOn** — incorporation date
- **sicCodes** — registered nature of business (e.g. 49410 freight transport by road)
- **companyName** — registered company name
- **⭐ isDistressed** — the moat signal: the operator's company is **dissolved / strike-off proposed / in administration / liquidation / receivership / voluntary arrangement / insolvency proceedings / removed** while their O-licence is still held (often still **valid**). The batch-validated UK rate is ~2–3% — operators running vehicles through a dying company. Time-sensitive, high-intent leads for insolvency practitioners, asset recovery, transport finance risk desks, leasing and competitor acquisitions — **not derivable from the raw DVSA file**.
- **companyNotFound** — a CRN was present but the **specific company lookup is a definitive not-found** (a real HTTP 404, or the CRN is a placeholder like `00000000` / `NULL`): the company does not exist. This is a per-CRN signal — it is **not** set on whole-outage runs and **not** set when a lookup merely failed (see `chDegraded` below)
- **chDegraded** — the **enrichment layer was unavailable for this record** (a per-record fetch error, or a suspicious fresh 404 on a suspected mass hard-404 outage): enrichment fields are null and `chDegraded=True`. Semantics: `companyNotFound` = this *specific* company **definitively 404'd**; `chDegraded` = the *enrichment service* was unavailable, so the not-found status of the record is *unknown* (never blanket-flag `companyNotFound` — a consumer pruning on it would wrongly drop every lead during an outage)
- **notEnrichable** — no usable company registration number. The ~28% of operators without a usable CRN (sole traders/partnerships) are dropped by the GDPR personal-data filter **before** enrichment, so they never reach the output and this flag is effectively always `false` in shipped rows — it exists for completeness of the enrichment layer.

The **distressed-operator filter** (`isDistressed` input) ships just those rows. Enrichment uses **keyless Companies House public pages** (rate-limited ~1.5 req/s per worker, up to 8 workers) with a **7-day cache** (KVS `CH_CACHE`) so weekly refreshes re-fetch only what changed or expired. **Bring-your-own-key (BYOK):** optionally supply a free **Companies House JSON API key** via the secret `chApiKey` input and the **official JSON API becomes the PRIMARY enrichment path** — a keyed full-UK/full-region scan runs entirely through the legit JSON API (no keyless 403-throttling), so it actually completes. Keyless public-page scraping stays primary when no key is given (fine for small or warm/cached runs but gets 403-throttled to ~0.5–1 req/s on sustained/full scans), so `chApiKey` is **recommended for full-UK / full-region enrichment** (`isDistressed`, `fullEnrichScan`, or `maxRows=0`). It is **the buyer's own key** (never a shared/our key), read only from the run input, never logged or stored, and paced at ~2 req/s (your quota, free tier = 600 req/5min) so it can't be hammered.

**A Companies House outage does not wipe out your leads.** A run whose CH enrichment is unhealthy behaves differently depending on the run type:

- **Bounded default run** (the daily auto-test shape: filters + `maxRows` cap before enrichment) and **monitor runs** never abort:
  - **Partial outage** (an elevated per-CRN fetch-failure rate, >5%): the records that fetched/cached OK **keep their enrichment**; only the failed subset is flagged (`chDegraded=True`, enrichment null, `companyNotFound` left false — the not-found status is unknown) with a loud `CH_ENRICH_FAILURE` warning — good data is never discarded.
  - **Mass hard-404 outage** (a suspected incident where **both** >10% of fresh lookups return a hard 404 — real 404s are clean `notFound`, invisible to the failure-rate canary — **and** at least 5 fresh lookups 404'd, so a tiny sample of genuine 404s can't false-trigger): only the **fresh-404 subset** is degraded (`chDegraded=True`, enrichment null, no `companyNotFound` blanket) while **pre-incident cached enrichment is preserved**, and the suspicious notFound entries are **NOT cached** — a CH incident cannot poison the 7-day notFound cache and suppress retries.
- **Full-scan / enrichment-focused runs** (`isDistressed=true` or `fullEnrichScan=true`) **fail loud** on either condition — enrichment is the *point* of those runs, so a CH outage aborts rather than shipping a degraded distressed-signal list.

**Enrichment is bounded to what ships.** With `isDistressed` off, the filters + `maxRows` cap apply BEFORE enrichment, so a default run (`maxRows=1000`) fetches ~1,000 CRNs (~1.5–2 min) instead of the whole UK fleet (~55–65K CRNs). `isDistressed=true` (and the explicit `fullEnrichScan` input) is a **full-scan mode** — the whole deduped fleet must be enriched before the flag can be filtered. The actor's default timeout is **7200s (2h)** — the default bounded run is ~90s (auto-test-safe). ⚠️ A cold full-UK `isDistressed` scan is a **LONG, SCHEDULED operation**: Companies House throttles sustained keyless scraping (real-world throughput is ~1 req/s effective, NOT the burst 12 req/s), so a single cold run will NOT complete in one 2h window. Run it on a schedule so the 7-day `CH_CACHE` accumulates across runs (each run warms more; the periodic flush makes resume safe), and/or provide a `chApiKey` (routes the full scan through the legit JSON API — no 403) for the full scan.

> **⚠️ `maxRows=0` + `region=all` + `enrich=true` is a full-UK enrichment scan.** Set **Max rows to 0** for the complete UK dataset. Combined with `region=all` and enrichment on (the default), the whole deduped fleet (~55–65K CRNs) is enriched before the cap. This is a **LONG, SCHEDULED operation** — CH throttles sustained keyless scraping (real-world ~1 req/s effective, NOT the burst 12 req/s), so a cold full-UK scan will NOT complete in one 2h window. Run it on a schedule so the 7-day `CH_CACHE` accumulates across runs, and/or provide a `chApiKey` (routes the full scan through the legit JSON API — no 403) for the full scan, or narrow the region. To keep a run fast, leave the default `maxRows=1000` (the daily auto-test shape).

**Default run fits the 5-minute auto-test.** The default shape (`region=all`, `maxRows=1000`, `enrich=true`) downloads/normalizes the 8 area CSVs (~30–60s) and enriches the ~1,000 capped CRNs (~1.5–2 min) — comfortably inside the 5-minute auto-test window. And because a CH outage ships degraded leads instead of aborting (above), an unrelated CH incident can no longer fail the auto-test.

**All cross-run state** (`SEEN_LICENCES`, `AREA_BASELINE`, `MONITOR_BASELINE`, `CH_CACHE`) lives in a **named key-value store** (`uk-fleet-leads-state`), which persists between runs — the default per-run store is discarded with each run and would silently reset delta mode, the drift canary, monitor baselines and the CH cache every time.

> **⚠️ Concurrent-runs warning.** This named store is **shared by all concurrent runs** of the actor. Because `SEEN_LICENCES`, `AREA_BASELINE`, `MONITOR_BASELINE` and `CH_CACHE` are read-modify-write, two runs in parallel will race and can clobber each other's state (e.g. a delta run's `SEEN_LICENCES` overwriting a full-refresh run's, or a monitor run's baseline). **Run this actor one at a time** (no overlapping scheduled runs) to guarantee state consistency.

> **GDPR note.** Enrichment is **company-status data only** — company status, incorporation date, SIC codes and registered name. Companies House **director and PSC (people with significant control) data is personal data and is NEVER fetched, processed, cached, or shipped**.

### Output fields (24 per record)

Each row includes: **operator** (business) name, licence number, licence type, **operator type**, **status** (valid / curtailed / revoked / suspended / surrendered), **fleet size** (vehicles + trailers), vehicles authorised, trailers authorised, correspondence address, locality address, continuation/renewal date, **company registration number**, region, two role flags (`hasNamedTransportManager`, `hasDirector`) — plus the enrichment layer above: `companyStatus`, `incorporatedOn`, `sicCodes`, `companyName`, `enrichmentSource`, `isDistressed`, `companyNotFound`, `notEnrichable`, and `chDegraded` (true only when a bounded run shipped leads with enrichment skipped due to a CH outage). Monitor-mode rows additionally carry `changeType`.

**Licence-level granularity.** A multi-licence operator appears once per licence, so you can segment by individual O-licence rather than by company.

### Monitor mode — track changes over time

Turn the weekly snapshot into a live watchlist. Run with **monitor on** and the actor diffs each refresh against its stored baseline, then outputs **only the operators that changed**:

- 🆕 **new** operators (just added to the register)
- ⚠️ **status changed** (e.g. valid → curtailed / revoked / suspended)
- 🚛 **fleet-size changed** (vehicles/trailers authorised changed)

Changed operators carry the same Companies House enrichment layer, so a monitor run can watch a segment (e.g. distressed-flagged operators) for new licence-holders or compliance trouble without re-scanning the full list every week. Ideal for fuel-card, telematics, insurance-renewal and driver-recruitment outreach that targets *new* or *risky* operators the moment they appear.

### Who is this for?

HGV fleet leasing & rental, fuel cards and telematics, HGV insurance, CPC / transport-manager training, driver recruitment & fleet management — plus **insolvency practitioners, asset recovery and transport finance risk desks** who buy the distressed-operator signal.

### Inputs & outputs

**Input:** region (`all` / `england` / `scotland` / `wales`), a full-refresh on/off switch, **Companies House enrichment on/off** (default **on**), the **distressed-companies-only** filter, an optional **bring-your-own Companies House API key** (`chApiKey`, secret — recommended for full-UK/full-region scans) + worker count, an optional max-rows cap (set **Max rows to 0** for the complete UK dataset; the default 1,000 keeps the daily auto-test fast), and **segmenting filters** so you pull exactly the operators you're selling to:

- **Licence type** — Standard National / Standard International / Restricted
- **Licence status** — valid / curtailed / revoked / suspended / surrendered
- **Fleet size range** — min / max vehicles authorised (e.g. 10+ for fuel-card or telematics deals)
- **Has transport manager** — only operators with a named transport manager in post
- **Operator type** — legal-entity type: Limited Company / Sole Trader / Partnership / Limited Liability Partnership / Other (public authority, charity, trust, university)
- **Distressed companies only** — only operators whose company is dissolved / strike-off / in administration / liquidation etc.

**Output:** dataset rows downloadable as JSON, CSV or Excel from the Apify UI, with an optional scheduled weekly refresh.

### Pricing

Pay-per-event: **$5.00 per 1,000 rows** (base `result` event, `apify-default-dataset-item`) — **enrichment included**. Rows flagged **distressed** (company dissolved / strike-off / administration / liquidation) are the enriched high-value output and carry a small **premium: +$2.00 per 1,000 distressed rows** (custom `distressed-result` event), so distressed rows are **$7.00 per 1,000 effective**. A full UK run costs roughly **$325** (all rows base price; the premium only applies to the distressed subset). Enrichment is compute, not per-row data cost: the CH public data is OGL-compatible and keyless.

### Data & licence

Official DVSA open data, refreshed **weekly on Sundays**. Contains public sector information licensed under the Open Government Licence v3.0. Companies House data is public data published under the Companies House (Open Government Licence) terms.

> **Data protection note.** This dataset is compiled from the DVSA/Office of the Traffic Commissioner operator licence register (Open Government Licence v3.0). The OGL does not cover personal data, and this product deliberately contains **no personal data**: it includes only operators that are legal persons — companies, LLPs and other non-personal entities (public authorities, charities, trusts, universities), identified by **company registration number AND legal-entity operator type**. Sole-trader and partnership operators are excluded **even when they carry a company registration number** (a company reg number alone does not make a natural person a legal person). The Companies House enrichment adds company-status data only — **no director or PSC data is processed**. Buyers remain responsible for their own lawful basis and, where applicable, PECR consent if they process or market to any identifiable individuals derived from this data.

That "no personal data" design is a real advantage: a clean, defensible list contractors and agencies can adopt without GDPR worry.

### Getting started

Pick a **region**, press **Start**, then open the **Dataset** tab and download your rows as **CSV or Excel**. For the distressed-operator slice, set **Distressed companies only = true**.

# Actor input Schema

## `region` (type: `string`):

Which traffic-commissioner region's operators to include (england = all six English TC areas).

## `fullRefresh` (type: `boolean`):

Re-download all CSVs. Off = only NEW licence numbers since the last run.

## `enrich` (type: `boolean`):

Enrich each operator with company-level Companies House data: companyStatus, incorporatedOn, sicCodes, companyName + the isDistressed flag (company dissolved / strike-off proposed / in administration / liquidation while the licence is still held). Rich data by default; the 7-day CH\_CACHE keeps repeat runs cheap. Company-status data only — no director or PSC data is ever fetched.

## `isDistressed` (type: `boolean`):

When true, only keep operators whose company is flagged distressed (dissolved / strike-off proposed / in administration / liquidation / receivership / voluntary arrangement / insolvency proceedings / removed). Requires enrichment (enrich=true). NOTE — PRICING: distressed-flagged rows are the enriched high-value output and carry a small premium: base rows are $5 per 1,000, distressed rows are $7 per 1,000 effective (+$2 per 1,000 custom distressed-result event). IMPORTANT: this is a FULL-SCAN mode — the whole deduped fleet must be enriched before the flag can be filtered. A cold full-UK scan (~55-65K CRNs) is a LONG, SCHEDULED operation: CH throttles sustained keyless scraping (real-world ~1 req/s effective, not the burst 12 req/s), so a single cold run will NOT complete in one 2h window. Run it on a schedule so the 7-day CH\_CACHE accumulates across runs (each run warms more; periodic flush makes resume safe), and/or provide a chApiKey (official JSON API, no 403) for the full scan. Actor default timeout is 7200s.

## `fullEnrichScan` (type: `boolean`):

When true, enrich the ENTIRE deduped fleet (ignoring the maxRows pre-cap) before filtering/capping — e.g. to warm the Companies House cache for the whole UK. Implied automatically when isDistressed=true. Requires enrichment (enrich=true); the actor errors clearly if combined with enrich=false. WARNING: Full UK ~55-65K CRNs is a LONG, SCHEDULED operation. CH throttles sustained keyless scraping (real-world ~1 req/s effective, not the burst 12 req/s), so a cold full-UK scan will NOT complete in one 2h window — run it on a schedule so the 7-day CH\_CACHE accumulates across runs, and/or provide a chApiKey (official JSON API, no 403) for the full scan, or narrow the region.

## `chApiKey` (type: `string`):

OPTIONAL — bring your OWN free Companies House REST API key (register at companieshouse.gov.uk / developer site). When supplied, the official CH JSON API becomes the PRIMARY enrichment path (paced at ~2 req/s against YOUR key's quota, free tier 600 req/5min) — a keyed full-UK/full-region scan runs entirely through the legit JSON API, no keyless 403-throttling. Empty/blank = keyless public-page scraping (fine for small or warm/cached runs; sustained/full scans get 403-throttled to ~0.5–1 req/s). RECOMMENDED for full-UK / full-region enrichment scans (isDistressed / fullEnrichScan / maxRows=0). This is ALWAYS your own key — never a shared or our key — read only from this run's input and never logged or stored.

## `chWorkers` (type: `integer`):

Concurrent Companies House fetchers (each paced at ~1.5 req/s; 8 workers ~= 12 req/s aggregate, full UK ~1h).

## `maxRows` (type: `integer`):

Cap on output rows (0 = unlimited; default 1000 keeps the run fast for the daily auto-test). WARNING: maxRows=0 combined with region=all and enrich=true triggers a FULL-UK enrichment scan (~55-65K CRNs). This is a LONG, SCHEDULED operation (CH throttles sustained keyless scraping — real-world ~1 req/s effective, not the burst 12 req/s), so a cold full-UK scan will NOT complete in one 2h window — run it on a schedule so the 7-day CH\_CACHE accumulates across runs, and/or provide a chApiKey for the full scan.

## `licenceType` (type: `string`):

Only keep operators with this licence type (empty = no filter).

## `licenceStatus` (type: `string`):

Only keep operators with this licence status (empty = no filter).

## `minFleetSize` (type: `integer`):

Only keep operators with fleet size >= this value (0 = no filter).

## `maxFleetSize` (type: `integer`):

Only keep operators with fleet size <= this value (0 = no upper bound).

## `hasTransportManager` (type: `boolean`):

When true, only keep operators with a named transport manager (false = no filter).

## `operatorType` (type: `string`):

Only keep operators of this legal-entity type (case-insensitive; 'Other' also matches DVSA's 'Other (e.g. public authority, charity, trust, university)'; empty = no filter). Note: sole traders and partnerships are excluded from the output entirely by the GDPR personal-data filter, so those two values always yield 0 rows (a warning is logged).

## Actor input object example

```json
{
  "region": "all",
  "fullRefresh": true,
  "enrich": true,
  "isDistressed": false,
  "fullEnrichScan": false,
  "chWorkers": 8,
  "maxRows": 1000,
  "licenceType": "",
  "licenceStatus": "",
  "minFleetSize": 0,
  "maxFleetSize": 0,
  "hasTransportManager": false,
  "operatorType": ""
}
```

# Actor output Schema

## `results` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("dthen/uk-fleet-leads-dvsa").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("dthen/uk-fleet-leads-dvsa").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 '{}' |
apify call dthen/uk-fleet-leads-dvsa --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dthen/uk-fleet-leads-dvsa"
        }
    }
}

```

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/Pwnq3BXx8s6yQ1b5N/builds/4YKkFvEbicldnBtGu/openapi.json
