# Which? Trusted Traders Scraper — Vetted UK Trade Leads (`scrapersdelight/which-trusted-traders-scraper`) Actor

Scrape Which?-endorsed UK tradespeople from Which? Trusted Traders: business name, trades and services, areas covered, phone, email, website, postcode, geo, rating, review count, endorsement date and award badges. 100% phone and email fill measured on 198 live rows. No login.

- **URL**: https://apify.com/scrapersdelight/which-trusted-traders-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 per trader returneds

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?

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

## Which? Trusted Traders Scraper — Vetted UK Trade Leads

Turn [Which? Trusted Traders](https://trustedtraders.which.co.uk) into a clean UK
tradesperson lead list. One row per endorsed business: **businessName, trades, services,
areasCovered, phone, email, website, streetAddress, postcode, addressRegion, latitude,
longitude, rating, reviewCount, endorsedSince, yearsEndorsed** and **accreditations**.
Filter by trade, town, county, rating, review count, years endorsed, or paste listing and
profile URLs. **100% of rows carry a phone number and 100% carry a contact email address**
— measured on 198 live profiles, not estimated. No login. No cookies. No CAPTCHA solving.

**Scope: 3,674 endorsed businesses in the entire UK** — counted live off Which?'s own
gzipped sitemap on 2026-08-12, not quoted from a marketing page. This Actor can deliver
every one of them for **$14.70**, which is a complete-directory offer no other UK trades
scraper can make.

```json
{
  "trades": ["plumbers"],
  "locations": ["london"],
  "maxItems": 100,
  "maxPagesPerSearch": 10
}
```

Click **Try for free** and hit **Start** — that block is literally the input the Actor
ships with. A run on those shipped defaults returned **100 rows in 1 minute 58 seconds**,
with phone 100%, email 100%, website 88.0%, rating 95.0% and award badges 15.0%.

***

### The wedge: an email address on every row

Which? Trusted Traders is the trade directory run by **Which?**, the UK consumer
association. A business only appears on it after Which? has run a credit check, an on-site
assessment and a customer-reference check — and it is re-assessed every year. That makes
these rows pre-qualified in a way a Google Maps pull never is, and it makes the directory
deliberately small and high-value: 3,674 businesses, not the hundreds of thousands a
self-serve directory carries.

The buying reason is the contact data. **Measured on the full 198-profile walk of
`plumbers-in-london`: phone 100%, email 100%.** A real mailbox, on the page, no contact
form in between. For comparison, inside our own UK trades family (all links at the bottom
of this page): **MyBuilder publishes no phone and no email at all**, and **FreeIndex
publishes no email**, so both need a separate email-finder step before you can send
anything. Here the email is just a column.

Two more things only this source has:

- **`endorsedSince` / `yearsEndorsed`** — the month Which? first endorsed the firm.
  64% of the 198-row sample had been endorsed 5+ years. Recency here is a real
  buying signal in both directions: a firm endorsed last quarter is buying tools and
  software now; a firm endorsed 9 years ago is an established business that renews.
- **`areasCovered`** — the towns and postcodes a trader actually travels to, averaging
  **11.4 per business**. That is a service radius, not a head-office pin.

As of 2026-08-12, Apify Store searches for "which trusted traders", "trusted traders",
"trustedtraders" and "vetted uk traders" return **no other Actor targeting
trustedtraders.which.co.uk**. That will not stay true forever, so judge this page on the
measurements below rather than on the absence of a rival.

***

### Read this before you buy rows

Five things that would otherwise turn into a refund request.

1. **"Years trading" does not exist on this site.** Which? publishes no company founding
   year. `yearsEndorsed` is *years endorsed by Which?*, not years in business. It is the
   only tenure signal the source has.
2. **The endorsement date lives on the listing card, not the profile page.** Scrape via
   Search or Listing URLs and you get the exact endorsement month. Paste **Profile URLs**
   and the Actor falls back to the profile's creation date, which runs a month or two
   earlier. Every row tells you which one you got in `endorsedSinceSource`.
3. **The directory is small, and a single trade in a single town runs out fast.**
   `plumbers-in-london` is 198 traders and that is the whole of it. If you ask for 1,000
   rows from one listing you will get 198 and a warning in the log, not an error. Add
   trades, add towns, or switch to sitemap mode.
4. **`primaryTrade` is the firm's own first trade, not the one you searched.** A firm
   found under "Plumbers" may lead with "Boiler, central heating and gas engineers". The
   trade you searched is kept separately in `searchTrade`.
5. **`rating` is `null` for the ~5% with no reviews yet, and `reviewCount` is `0`** for
   them — a real zero, not a missing value. Any `minRating` above 0 removes those traders.

***

### What you get

One row per unique Which?-endorsed business. All dates are ISO-8601 (`endorsedSince` is
`YYYY-MM`, `profileCreatedAt` / `profileUpdatedAt` are `YYYY-MM-DD`, `scrapedAt` is a full
UTC timestamp). `rating` is a number out of **5**. Phones are E.164.

| Group | Fields | Example |
|---|---|---|
| **Identity** | `businessName`, `profileUrl`, `businessSlug`, `whichId`, `status`, `whichEndorsed` | `AZ Plumbing Contractors Limited` · `03bc54d0fffa01343b5346f4283aad24` |
| **Trade** | `primaryTrade`, `trades[]`, `services[]` | `Boiler, central heating and gas engineers` · 5 trades · 8 services |
| **Contact** | `phone`, `phoneDisplay`, `phones[]`, `email`, `emails[]`, `website` | `+447712507601` · `07712507601` · `info@azplumbingcontractors.co.uk` |
| **Address** | `streetAddress`, `addressLocality`, `addressRegion`, `postcode`, `latitude`, `longitude` | `Flat B, 1 Grantbridge Street` · `London` · `Greater London` · `N1 8JL` · `51.5344, -0.100203` |
| **Coverage** | `areasCovered[]`, `milesFromSearchCentre` | `["London, England", "Islington", "Hackney", … , "N1"]` |
| **Reputation** | `rating`, `reviewCount`, `ratingBreakdown{}`, `accreditations[]` | `4.9` · `118` · `{Customer Service: 4.9, Quality: 4.9, Value: 4.8}` |
| **Tenure** | `endorsedSince`, `endorsedSinceLabel`, `yearsEndorsed`, `endorsedSinceSource` | `2017-05` · `May 2017` · `9` · `listing-card` |
| **Profile** | `description`, `logo`, `openingHours[]`, `galleryImageCount`, `profileCreatedAt`, `profileUpdatedAt`, `profileCompleteness` | `2026-07-23` · `100` |
| **Provenance** | `searchTrade`, `searchLocation`, `listingUrl`, `scrapedAt` | `Plumbers` · `london` |

The dataset ships with three saved **table views**, so you do not have to configure
columns: *Which?-endorsed UK traders* (the overview), *Lead generation — contact columns
only* (name, trade, phone, email, website, address) and *Vetting — ratings, tenure and
awards*.

***

### Field fill — measured on 198 traders

The full walk of `plumbers-in-london` to exhaustion — 17 listing pages, 198 profiles, 15
distinct primary trades, 2026-08-12. Sorted by fill, so the sparse fields are impossible
to miss.

| Field | Fill | Notes |
|---|---|---|
| `businessName` | 100% | |
| **`phone` / `phoneDisplay` / `phones`** | **100%** | E.164 (`+4420…`) and the printed form; 45% list a second number |
| **`email` / `emails`** | **100%** | a real mailbox, not a contact form |
| `primaryTrade` / `trades` | 100% | the full endorsed trade list; **4.6 per business** on average |
| `postcode` | 100% | full UK postcode |
| `streetAddress` / `addressLocality` | 100% | street line and town |
| `latitude` / `longitude` | 100% | |
| `areasCovered` | 100% | towns/postcodes they actually travel to — **11.4 per business** |
| `reviewCount` | 100% | `0` is a real 0, not a null |
| `endorsedSince` / `endorsedSinceLabel` / `yearsEndorsed` | 100% | `2017-05` · `May 2017` · `9` |
| `whichId` | 100% | the stable Which? business id |
| `profileCreatedAt` / `profileUpdatedAt` | 100% | how fresh the profile is |
| `profileCompleteness` | 100% | Which?'s own 0–100 score for the profile |
| `description` | 100% | the trader's own copy |
| `services` | 97.5% | individual jobs under each trade — **16.2 per business** |
| `logo` | 96.5% | |
| `rating` / `ratingBreakdown` | 94.9% | out of 5, plus customer-service / quality / value sub-scores |
| **`website`** | **89.4%** | about one in nine publishes none |
| `openingHours` | 82.8% | |
| `addressRegion` | 80.3% | county; only printed when Which? holds one |
| **`accreditations`** | **14.6%** | award badges, e.g. "Certificate of Distinction (Jun 2020)" |

**Contact-only mode (`fetchProfileDetails: false`), measured on 117 Manchester rows:**
phone 100% · email 99.1% · rating 94.9% · postcode 100% · geo 100%. Every other column is
still present and `null`, so the schema never changes between modes.

**The headline that could mislead you: `yearsEndorsed` is not company age.** It is years
since Which? endorsed the firm, and it is 100% filled precisely because it is a Which?
field rather than a company-registry one. If you need incorporation dates, that is a
Companies House job, not this one.

Plan against the 198-row table, and remember it is one trade in one city. **Every run
prints its own measured fill in the log** — the shipped-default 100-row run printed phone
100%, email 100%, website 88.0%, rating 95.0%, services 97.0%, areas covered 100%,
endorsed-since 100%, award badges 15.0%, which is that same London listing sampled at 100
rows instead of 198.

***

### How to run it

#### 1. Search — trade + location (the usual choice)

```json
{
  "source": "search",
  "trades": ["plumbers", "electricians"],
  "locations": ["london", "manchester"],
  "maxPagesPerSearch": 10,
  "maxItems": 200
}
```

Walks `https://trustedtraders.which.co.uk/{trade}-in-{location}/` — 12 businesses per page
— and stops on its own when a listing runs out. Every trade is crossed with every location,
so 2 × 2 = 4 listings here. Free text is slugified: `"Painters & Decorators"` becomes
`painters-and-decorators`.

**Listing aliases are followed on any page.** Which? runs many slugs as aliases of a
canonical one, and the redirect can land on page 1 *or* deeper: `electricians-in-leeds`
serves a normal page 1 and then redirects page 2 to `electricians-in-leeds-west-yorkshire`.
Read naively that looks like the end of the listing and quietly loses 14 of its 26 traders.
This Actor tells the two redirects apart — a different slug is an alias to keep walking, the
same slug with a page number is the end — and every listing's take is checked against the
"1-12 of N" count the page advertises, with a warning if it comes up short.

#### 2. Sitemap — the whole UK directory

```json
{ "source": "sitemap", "maxItems": 4000 }
```

Reads Which?'s own gzipped sitemap and queues **all 3,674 endorsed businesses**. That
sitemap held 5,772 URLs on 2026-08-12, of which 3,674 are `/businesses/` profiles — this is
the complete directory, there is nothing else to get. Combine with `skip` to page through
it across several runs.

#### 3. Profile URLs — specific businesses

```json
{
  "source": "profileUrls",
  "profileUrls": ["https://trustedtraders.which.co.uk/businesses/az-plumbing-contractors/"]
}
```

Re-check a list of traders you already hold. Remember gotcha 2 above: this route sets
`endorsedSinceSource: "profile-created"`.

#### 4. Listing URLs — paste a page from the site

```json
{ "startUrls": ["https://trustedtraders.which.co.uk/tilers-in-london/"] }
```

The escape hatch for any trade or town not in the slug lists below: find the page on
Which?, paste it, and the Actor adds the page numbers itself. Works alongside
`trades` + `locations`.

#### Contact-only mode — 12× fewer requests, same price

Set `"fetchProfileDetails": false` and the Actor returns only what the listing card
publishes — name, phone, email, postcode, geo, rating, review count and endorsement date —
without opening a single profile page. Fill in this mode over 117 Manchester rows: phone
100%, email 99.1%, rating 94.9%, postcode 100%, geo 100%. **The price per trader is
identical either way** — this buys time, not money. What you give up: website, street
address, county, the full trade and service lists, areas covered, award badges, opening
hours and the description.

#### Filters

`onlyWithPhone` · `onlyWithEmail` · `onlyWithWebsite` · `minRating` · `minReviewCount` ·
`minYearsEndorsed`. Everything except `onlyWithWebsite` is applied **from the listing card,
before a profile page is ever fetched**, so filtered-out traders cost you nothing and the
run is faster. `onlyWithWebsite` is the exception — the website only exists on the profile
page, so that filter forces profile fetching and makes the Actor queue ~3× the candidates.
Rows removed by any filter are never billed.

#### Resuming across runs

```json
{ "source": "sitemap", "maxItems": 500, "skip": 500 }
```

Run 1 takes traders 1–500, run 2 with `skip: 500` takes 501–1,000. You never re-buy a row
you already have.

#### Scheduling and integrations

Save the input as a **Task** (Console → the Actor → *Save as Task*), then attach an Apify
**Schedule** — `0 6 * * 1` for a Monday-morning refresh of your target towns. The dataset
is available over the REST API, and through the standard Apify integrations (Zapier, Make,
n8n, webhooks, MCP) like any other Actor. Starting a run from the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~which-trusted-traders-scraper/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"trades":["electricians"],"locations":["bristol"],"maxItems":50}'
```

***

### Sample row

A real row from an actual run, captured 2026-08-13 01:47 UTC (the description is truncated
here for length; the field is full-length in the dataset).

```jsonc
{
  "businessName": "AZ Plumbing Contractors Limited",
  "profileUrl": "https://trustedtraders.which.co.uk/businesses/az-plumbing-contractors/",
  "businessSlug": "az-plumbing-contractors",
  "whichId": "03bc54d0fffa01343b5346f4283aad24",
  "status": "endorsed",
  "whichEndorsed": true,

  "primaryTrade": "Boiler, central heating and gas engineers",
  "trades": ["Boiler, central heating and gas engineers", "Heating contractors", "Plumbers", "Gas installers", "Renewable energy"],
  "services": ["Gas cooker installation", "Gas emergencies", "Gas safety testing and inspection", "Radiators and central heating", "Underfloor heating", "Central heating systems (installation and servicing)", "Emergency plumbing services", "Unvented cylinder installation and maintenance"],

  "phone": "+447712507601",
  "phoneDisplay": "07712507601",
  "phones": ["+447712507601", "+442073540460"],
  "email": "info@azplumbingcontractors.co.uk",
  "emails": ["info@azplumbingcontractors.co.uk"],
  "website": "http://www.azplumbingcontractors.co.uk",

  "streetAddress": "Flat B, 1 Grantbridge Street",
  "addressLocality": "London",
  "addressRegion": "Greater London",
  "postcode": "N1 8JL",
  "latitude": 51.5344,
  "longitude": -0.100203,
  "areasCovered": ["London, England", "Islington", "Hackney", "Kensington And Chelsea, West London", "Holloway, Islington", "Camden, North West London", "Muswell Hill, Haringey", "Richmond Upon Thames, South West London", "Barbican, London", "Kings Cross, Islington", "N1"],

  "rating": 4.9,
  "reviewCount": 118,
  "ratingBreakdown": { "Customer Service": 4.9, "Quality": 4.9, "Value": 4.8 },
  "accreditations": ["Certificate of Distinction (Jun 2020)", "Islington County Council"],

  "endorsedSince": "2017-05",
  "endorsedSinceLabel": "May 2017",
  "endorsedSinceSource": "listing-card",
  "yearsEndorsed": 9,
  "profileCreatedAt": "2017-04-10",
  "profileUpdatedAt": "2026-07-23",
  "profileCompleteness": 100,

  "openingHours": ["Mo-Su: 24 hours"],
  "galleryImageCount": 12,
  "description": "AZ Plumbing Contractors Limited are a local family run plumbing and heating company based in Islington, North London. All our engineers and plumbers are qualified with over 10 years experience…",
  "logo": "https://production-which-local.s3.eu-west-1.amazonaws.com/uploads/image/file/52054/gallery_large_logo_2.png",

  "searchTrade": "Plumbers",
  "searchLocation": "london",
  "listingUrl": "https://trustedtraders.which.co.uk/plumbers-in-london/",
  "milesFromSearchCentre": 3,
  "scrapedAt": "2026-08-13T01:47:05.107Z"
}
```

Fields people misread:

- `rating` is **out of 5**, and `ratingBreakdown` uses the same 0–5 scale.
  `profileCompleteness` is a **0–100** score. Do not put them in one column.
- `reviewCount: 0` means "reviewed by nobody"; `rating: null` means Which? published no
  rating (which is the same ~5% of traders).
- `phone` is the first number, E.164-normalised. `phones` is every number on the page —
  45% of traders publish a second one.
- `milesFromSearchCentre` only exists on rows found through a search; it is the distance
  Which? printed on the card, relative to the town you searched.

***

### Input

Fields in the order the Console shows them.

| Field | Type | Default | What it does |
|---|---|---|---|
| **🎯 What to scrape** | | | |
| `source` | select | `search` | `search` = trade × location listings · `sitemap` = the whole 3,674-business directory · `profileUrls` = only what you paste. |
| `trades` | string list | `["plumbers"]` | Trades to search. Free text is slugified. Vocabulary below. |
| `locations` | string list | `["london"]` | Towns, cities, counties or regions. Crossed with every trade: 2 × 3 = 6 listings. |
| `startUrls` | listing URLs | `[]` | Paste Which? listing pages. Page numbers are added for you. |
| `profileUrls` | string list | `[]` | Paste business profile pages. Scraped whatever `source` says. |
| **🎚️ Lead-quality filters** | | | |
| `onlyWithPhone` | boolean | `false` | Fill is 100% on 198 rows, so this normally removes nothing. |
| `onlyWithEmail` | boolean | `false` | Fill is 100% on 198 rows, so this normally removes nothing. |
| `onlyWithWebsite` | boolean | `false` | Drops ~1 in 9. The only filter that needs the profile page, so it forces profile fetching. |
| `minRating` | select | `0` | Out of 5. Anything above 0 also drops the ~5% with no reviews. |
| `minReviewCount` | integer | `0` | Social-proof floor. |
| `minYearsEndorsed` | integer | `0` | Years since Which? endorsed them — not years in business. 64% are 5+. |
| **📦 Output shape** | | | |
| `fetchProfileDetails` | boolean | `true` | Off = listing card only, 12× fewer requests, same price per trader. |
| `includeDescription` | boolean | `true` | The "About" text. Off for a slim CSV. |
| `includeServices` | boolean | `true` | 16.2 services per trader on average. |
| `includeOpeningHours` | boolean | `true` | Present on 82.8% of rows. |
| **💷 Limits & cost** | | | |
| `maxItems` | integer | `100` | **Your hard cost cap.** 100 = $0.40, 1,000 = $4.00, all 3,674 = $14.70. |
| `maxPagesPerSearch` | integer | `10` | 12 traders per page, so 10 pages ≈ 120 per trade × town. |
| `skip` | integer | `0` | Resume across runs without re-buying rows. |
| **⚙️ Advanced** | | | |
| `maxConcurrency` | integer | `3` | Max 5. Measured 215/215 = 100% at concurrency 4. |
| `requestDelayMs` | integer | `500` | ≈2 requests/second at concurrency 3. |
| `proxyConfiguration` | proxy | Apify proxy (datacenter) | Which? is not walled — the cheap rung is the default on purpose. |

#### Trade slugs

The 23 trades Which? itself lists as popular, with the site's own names:

`plumbers` · `electricians` · `builders` · `roofers` · `gardeners` · `carpenters` ·
`plasterers` · `glaziers` · `painters-and-decorators` · `boiler-installation` ·
`central-heating-installation-and-servicing-` (the trailing hyphen is Which?'s, not a typo)
· `bathroom-fitters-and-designers` · `kitchen-installation` · `heat-pump-installers` ·
`burglar-alarms` · `carpet-cleaners` · `computer-repairs` ·
`damp-proofing-and-timber-preservation` · `drain-and-sewer-services` · `home-removals` ·
`aerial-and-satellite-installers` · `motor-vehicle-mechanics` · `tyre-fitters`

Which?'s sitemap runs listing pages for 29 trade slugs, adding `interior-designers`,
`landscape-designers`, `renewable-energy`, `garages`, `painters` and `removals`. The site
serves plenty more besides — these all returned trade-specific results in London on
2026-08-12: `locksmiths`, `tilers`, `fencing`, `driveways`, `flooring`, `pest-control`,
`tree-surgeons`, `scaffolding`, `solar-panel-installers`, `window-cleaners`,
`gas-installers`, `heating-contractors`. Anything else: find the page on Which? and paste
it into `startUrls`.

#### Location slugs

Which?'s sitemap publishes 67 area slugs — mostly counties and regions
(`hertfordshire`, `west-sussex`, `leicestershire`, `buckinghamshire`, `warwickshire`,
`northamptonshire`, `nottinghamshire`, `worcestershire`, `berkshire`, `bedfordshire`,
`herefordshire`, `surrey`, `east-sussex`, `greater-manchester`, `west-midlands`,
`west-yorkshire`, `south-yorkshire`, `merseyside`, `carmarthenshire`, `ceredigion`,
`pembrokeshire`, `monmouthshire`, `denbighshire`, `conwy`, `isle-of-anglesey`, `fife`,
`stirling`, `angus`, `orkney`, `morayshire`, `inverclyde`, `north-ayrshire`,
`south-ayrshire`, `east-ayrshire`, `south-lanarkshire`, `edinburgh-and-lothian`,
`south-eastern-ni`, `peak-district`, `yorkshire-dales`, `south-downs`, `dartmoor`,
`exmoor`, `brecon`, `ribble-valley`, `norfolk-broads`, `huntingdonshire`, `lairg`, `cowal`,
`bristol-and-surrounding-areas`) — plus cities (`london`, `bristol`, `leeds`, `nottingham`,
`glasgow`, `birmingham`, `edinburgh`, `manchester`, `sheffield`, `liverpool`, `cardiff`,
`brighton`, `leicester`, `oxford`, `plymouth`, `southampton`, `reading`, `northampton`).

Towns outside that list often work too — `york`, `norwich`, `coventry`, `luton` and
`milton-keynes` each returned a full page when checked live on 2026-08-12, and some towns
(`croydon`) are aliases Which? redirects, which the Actor follows rather than treating as
the end of a listing. A slug Which? does not run returns an empty page rather than a 404,
and the Actor stops with a list of live slugs instead of handing you nothing.

***

### Pricing

**$0.004 per trader returned — $4 per 1,000.** Charged on the `trader-scraped` event.
No monthly platform fee from this Actor.

| Run | Traders | Cost |
|---|---|---|
| The shipped default (London plumbers) | 100 | **$0.40** |
| One trade across four cities | 500 | **$2.00** |
| A full outbound list | 1,000 | **$4.00** |
| **The entire UK directory** | **3,674** | **$14.70** |

- **You are charged for rows delivered.** A trader fetched and then removed by your filters
  is never charged. A trader that appears in three different trade listings is charged
  once — duplicates are dropped before billing.
- **Rows are charged as they are pushed** (`Actor.pushData(items, 'trader-scraped')`), so
  if you hit a budget cap you get whole rows and stop, never a half-billed dataset.
- **`maxItems` is your hard cost cap** (default 100). It counts unique, delivered traders.
- Contact-only mode costs the same per trader as the full profile mode. It saves time and
  our proxy budget, not your money.

***

### Honest limits

- **No company founding year, anywhere.** Which? does not publish one, so nothing here can
  tell you a firm's age. `yearsEndorsed` (100% filled) is years since Which? endorsed
  them, which is a different thing. If you need incorporation dates, join on Companies
  House.
- **Award badges are rare — 14.6%** on 198 rows. Absence of a badge says nothing bad about
  a trader: every business in this dataset is Which?-endorsed by definition. Do not build a
  quality filter on `accreditations`.
- **10.6% publish no website** and **~5% have no rating at all** (rating fill 94.9%). Those
  are Which? data gaps, not scraping failures. The upside: the 10.6% with no website, and
  the low-`profileCompleteness` rows, are a ready-made target list if you sell websites.
- **`addressRegion` is 80.3%** — Which? only prints a county when it holds one. Postcode
  (100%) and lat/lon (100%) are the reliable geography.
- **The whole directory is 3,674 businesses.** That is the ceiling, and no input can raise
  it. Which? is a vetted scheme, not a mass directory — if you need tens of thousands of UK
  trade rows, this is the wrong source, and our Checkatrade and FreeIndex Actors (below)
  read open directories instead.
- **Profile-URL runs lose the exact endorsement month** and fall back to profile creation
  date, a month or two earlier. `endorsedSinceSource` tells you which you got.
- **No login, no cookies, no CAPTCHA solving service.** Everything here is public
  directory data, and nothing behind Which?'s member login is touched.
- **UK only.** Which? Trusted Traders is a UK scheme; there are no non-UK rows to get.

***

### How it works, and what it cost to make reliable

Three enumeration routes (search listings, the gzipped sitemap, pasted URLs), then one
fetch per business profile, where three sources are read at once — the schema.org JSON-LD,
the in-page `window.universal_variable` state blob, and the rendered HTML. No single one of
them carries the whole record, which is why the parser reads all three. No login, no API
key, no browser automation.

**Transport ladder, measured 2026-08-12 through Apify:**

| Rung | Result |
|---|---|
| Direct, no proxy (reported and then ignored — no Apify container has a home IP) | 200, 12/12 cards |
| Apify proxy, auto / datacenter | 200, 12/12 cards |
| Apify RESIDENTIAL | 200, 12/12 cards |
| Apify RESIDENTIAL + country GB | 200, 12/12 cards |

**Which? is not walled**, so the default is the cheap datacenter rung and you are not
billed for residential IPs you do not need. Sustained load, all measured through the Apify
proxy:

| Test | Result |
|---|---|
| 30 profile pages spread across the whole sitemap, concurrency 4, datacenter | **29 / 30 = 96.7%**, 2.22 req/s |
| Same test through RESIDENTIAL + GB | 29 / 30 = 96.7%, 1.63 req/s |
| Full `plumbers-in-london` run: 17 listing pages + 198 profiles, concurrency 4 | **215 / 215 = 100%** |
| Parse success on those 198 profiles | **198 / 198 = 100%** |

The single non-200 in the sitemap sample was an HTTP 302 to `/missing_business/` — a
business Which? has **de-endorsed** — not a block. The Actor counts those separately and
never reports them as failures.

`robots.txt` declares **no crawl-delay**. It disallows `/users/`, `/signin/`, `/forgot`,
`/iframe/`, `/feedbacks/new` and the `?sort_type=` / `?distance=` / `?relevance=` /
`?endorsed_only=` filter parameters — **none of which this Actor touches**. Pagination uses
the bare `/{trade}-in-{location}/{page}/` path, verified to return the same 12 businesses in
the same order as the parameterised link the site itself renders. Defaults stay gentle:
concurrency 3 with a 500 ms delay per worker, about 2 requests/second.

***

### Duplicates — measured in both directions

Measured on a **contiguous** walk of `plumbers-in-london` (2026-08-12):

| Walk | Business links returned | Unique businesses | Duplicates |
|---|---|---|---|
| Pages 1–12, contiguous | 144 | 144 | **0.0%** |
| Full walk to exhaustion (17 pages) | 198 | **198** | **0.0%** |

There are no promoted or sponsored slots on a Which? listing page at all, so a single
listing walk repeats nothing. (Our sibling TrustATrader Actor exists partly to strip 3
promoted adverts pinned to every listing page — a 23.1% per-page duplicate rate. Which?
does not do that.)

Duplicates **do** arise from a second source, and this Actor drops them: a Which? trader is
endorsed for an average of **4.6 trades**, so searching several trades in the same town
returns the same firm repeatedly. Measured on plumbers + electricians + builders in
Manchester: 126 links → 117 unique businesses = **7.1% duplicates dropped**. The Actor
keeps a **run-wide set keyed on the canonical profile URL**, deduplicates *before* anything
is fetched or billed, and then **asserts uniqueness across everything it pushed** before
the run is allowed to succeed. If that assertion ever fails, the run errors instead of
handing you a dirty dataset. Every run logs the exact duplicate count it dropped.

Across runs, use `skip` — dedupe is per-run by design.

***

### When a run fails

This Actor fails loudly rather than handing back a quiet empty dataset:

- **0 rows** → it throws, and the message names the scope and the HTTP counts (profiles
  fetched, network failures, de-endorsed, unparseable, removed by your filters, removed on
  card data) so you can tell a filter problem from a site problem.
- **Bad trade or town slug** → Which? does *not* 404; it serves an empty results page
  (verified: `zzqqfoobar-in-london` and `plumbers-in-zzqqfoobar` both return HTTP 200 with
  zero cards). The Actor catches that on page 1 and errors with **a list of live slugs to
  use instead**, pulled from the sitemap.
- **Markup change** → if more than 20% of HTTP 200 pages carry neither schema.org JSON-LD
  nor the profile state blob, the run errors instead of shipping a degraded extraction.
- **Shrunken sitemap** → whole-directory mode refuses to run if the sitemap holds fewer
  than 2,000 businesses, rather than passing off a partial directory as the whole one.
- **Short listing** → if a listing yields less than 90% of the result count it advertises,
  and neither your page limit nor your item limit explains the gap, the run warns rather
  than quietly handing you a truncated town.
- **Duplicate leak** → the uniqueness assertion fails the run.

A run that returns fewer rows than you asked for because the directory ran out is a
**warning, not an error** — the log tells you how many were left.

***

### Who buys this

- **Trade suppliers, tool brands and merchants** building a UK installer list segmented by
  trade, service and town — `services` averages 16.2 rows of "what they actually do".
- **Trade software and SaaS (job management, quoting, payments — Tradify, Powered Now,
  Commusoft and the like)** — a Which?-endorsed firm is an established business that
  already pays for things, and 100% of them have an email you can write to.
- **Trade insurance and finance brokers** targeting vetted, annually re-assessed
  businesses; `yearsEndorsed` sorts renewal-cycle prospects from new entrants.
- **Agencies selling websites and marketing to trades** — the 10.6% with no website and the
  low-`profileCompleteness` rows are the target list, and the email is already there.
- **Franchise and trade-association recruiters** — `areasCovered` (11.4 areas per firm)
  shows who already covers a territory you are trying to fill.
- **UK home-services market researchers** — real service radii and endorsement cohorts, not
  a head-office pin.

***

### Our other UK trades Actors

| Actor | What it is | Why you would use it instead |
|---|---|---|
| [Checkatrade Scraper](https://apify.com/scrapersdelight/checkatrade-scraper) | UK tradespeople by trade + town, rating **out of 10**, review counts, sample reviews, services | An open directory rather than an endorsement scheme. Watch the scale — its rating is out of 10, ours is out of 5 |
| [MyBuilder Scraper](https://apify.com/scrapersdelight/mybuilder-scraper) | Ranked tradespeople per trade + city, badges, verification status, review snippets, position in list | For job-marketplace ranking signals — **but MyBuilder publishes no phone and no email** |
| [Rated People Scraper](https://apify.com/scrapersdelight/rated-people-scraper) | UK traders with ratings out of 5, full reviews and a **service-area polygon** (lat/lng) | When you need the actual mapped service area; its phone field is often absent |
| [FreeIndex UK Scraper](https://apify.com/scrapersdelight/freeindex-uk-scraper) | General UK business directory: phone, website, full postal address, geo, rating | Any FreeIndex category, not just trades — **but no email is published**, so pair it with an email-finder |

Which? is the one you buy when you want **vetted firms with a phone and an email on every
row**. If 3,674 businesses is not enough volume, the others cover open directories that
are not limited to an endorsement scheme.

***

### FAQ

**Does this need an account, a login or cookies?**
No. Everything read here is a public directory page. There is no login, no cookie jar, no
CAPTCHA solving service and no browser automation.

**Do I really get email addresses?**
Yes — 100% of the 198-profile `plumbers-in-london` walk carried one, and 100% of the
default 100-row run on 2026-08-12. They are real mailboxes printed on the profile, not
contact-form links. In contact-only mode it measured 99.1% over 117 Manchester rows.

**Which trades and towns work?**
Any trade × location page Which? runs. 23 popular trades and 67 area slugs are listed
above, more are verified working, and a slug that does not exist stops the run with a list
of live ones instead of returning nothing.

**Can I get the whole directory in one run?**
Yes — `{"source": "sitemap", "maxItems": 4000}` queues all 3,674 endorsed businesses for
$14.70. That is the complete UK directory; there is no bigger number to get.

**What is "endorsed since" versus years trading?**
Which? publishes no founding year. `endorsedSince` is the month Which? first endorsed the
firm, and `yearsEndorsed` is that in whole years. 64% of the London sample were 5+ years
endorsed.

**What does contact-only mode change?**
`fetchProfileDetails: false` skips every profile page: 12× fewer requests, and you keep
name, phone, email, postcode, geo, rating, review count and endorsement date. You lose
website, street address, county, full trades/services, areas covered, badges, opening hours
and the description. Same price per trader.

**Do I need a residential proxy?**
No. Direct, datacenter and residential all returned HTTP 200 in testing, so the default is
the cheap datacenter rung — 215/215 = 100% on a sustained load. RESIDENTIAL + GB is the
documented fallback if you ever see failures.

**Two runs — will I get duplicates?**
Within a run, never: dedupe is on the canonical profile URL and is asserted before the run
can succeed. Across runs, use `skip` to resume where you stopped.

**Do I get charged for rows a filter removed?**
No. Filters other than `onlyWithWebsite` are applied from the listing card before any
profile is fetched, and nothing filtered out is ever pushed or billed.

**Will a run ever succeed with zero rows?**
No. Zero rows throws, with the scope and the HTTP counts in the message. A run that returns
fewer rows than you asked for because the directory ran out logs a warning and succeeds.

**Something looks wrong — how do I debug it?**
Read the run log. Every run prints its own enumeration counts, duplicate count, measured
field fill and the uniqueness verification. That is usually enough to tell a filter problem
from a Which?-side change.

**Is this legal under UK GDPR?**
The data is public, but rows can contain personal data — see below. Lawful scraping and
lawful marketing are two separate questions, and the second one is yours.

***

### Legal & fair use

This Actor reads public pages on trustedtraders.which.co.uk and the sitemap Which?
publishes in its own `robots.txt`. It does **not** log in, does not touch any path Which?
`Disallow`s (`/users/`, `/signin/`, `/forgot`, `/iframe/`, `/feedbacks/new`, and the
`?sort_type=` / `?distance=` / `?relevance=` / `?endorsed_only=` filter parameters), and
collects nothing behind any authentication.

Records can contain personal data — sole-trader names, mobile numbers and personal email
addresses. **You are responsible for complying with Which?'s terms of service and with how
you use the data**, including UK GDPR, PECR and the rules on unsolicited B2B email and
telephone marketing.

Which?® and Which? Trusted Traders® are trademarks of their owner. This Actor is not
affiliated with, endorsed by, or connected to Which? Limited.

***

### Feedback

Found a missing field or want a new filter? Open an issue on the **Issues** tab, and if the
Actor earns it, a review on the **Reviews** tab helps other buyers find it.

# Actor input Schema

## `source` (type: `string`):

SEARCH walks the trade + location listing pages (e.g. plumbers in London) — the usual choice. SITEMAP takes the WHOLE directory from Which?'s own gzipped sitemap (3,674 endorsed businesses, counted live on 2026-08-12). PROFILE URLS scrapes only the business pages you paste in. All three produce identical rows, and all three cost the same per trader.

## `trades` (type: `array`):

Trades to search, as they appear in a Which? URL. Free text is slugified, so "Painters & Decorators" becomes "painters-and-decorators" — you can type the trade as a person would say it. The 23 trades Which? itself lists as popular: plumbers · electricians · builders · roofers · gardeners · carpenters · plasterers · glaziers · painters-and-decorators · boiler-installation · central-heating-installation-and-servicing- (note the trailing hyphen, it is Which?'s own slug) · bathroom-fitters-and-designers · kitchen-installation · heat-pump-installers · burglar-alarms · carpet-cleaners · computer-repairs · damp-proofing-and-timber-preservation · drain-and-sewer-services · home-removals · aerial-and-satellite-installers · motor-vehicle-mechanics · tyre-fitters. Also verified live on 2026-08-12: locksmiths, tilers, fencing, driveways, flooring, pest-control, tree-surgeons, scaffolding, solar-panel-installers, window-cleaners, garages, interior-designers, landscape-designers, renewable-energy, gas-installers, heating-contractors. A slug Which? does not run returns an empty page rather than a 404, so the run stops with a list of live slugs pulled from the sitemap instead of quietly returning nothing. Used only when Source is SEARCH.

## `locations` (type: `array`):

UK places, combined with every trade above — 2 trades × 3 locations = 6 listings walked. Three granularities all work: a city (london, manchester, birmingham, leeds, glasgow, edinburgh, cardiff, bristol, sheffield, liverpool), a county (hertfordshire, west-sussex, fife, leicestershire, pembrokeshire) or a region (greater-manchester, west-midlands, west-yorkshire, merseyside). Which? publishes 67 area slugs in its own sitemap (london, manchester, leeds and greater-manchester are all among them) and accepts many towns beyond that list — york, norwich, coventry, luton and milton-keynes each returned a full page when checked live on 2026-08-12. If a slug does not exist the run stops with an error listing live slugs to use instead. Used only when Source is SEARCH.

## `startUrls` (type: `array`):

Paste Which? listing pages directly instead of, or alongside, trades + locations — e.g. https://trustedtraders.which.co.uk/electricians-in-manchester/. This is the escape hatch for any trade Which? runs that is not in the list above: find the page on the site, paste it here. Page numbers are added automatically; do not include one.

## `profileUrls` (type: `array`):

Specific business pages to scrape, e.g. https://trustedtraders.which.co.uk/businesses/az-plumbing-contractors/. Anything pasted here is scraped whatever Source is set to — useful for re-checking a list of traders you already hold. Note that rows scraped this way carry endorsedSinceSource: "profile-created" (see the Actor's README): the endorsement month only exists on the listing card, so this route falls back to the profile creation date, which runs a month or two earlier.

## `onlyWithPhone` (type: `boolean`):

Measured fill is 100% on 198 live London rows, so this normally removes nothing. Leave it off unless you are pasting profile URLs of unknown quality.

## `onlyWithEmail` (type: `boolean`):

Measured fill is 100% on 198 live London rows — Which? publishes a real contact mailbox on essentially every endorsed business, which is what makes this directory unusually good for outbound. Like the phone filter, this normally removes nothing.

## `onlyWithWebsite` (type: `boolean`):

89.4% of Which? traders publish a website (measured on 198 rows), so this discards roughly one in nine. The website only exists on the profile page, so this is the ONE filter that cannot be applied from the card: switching it on forces profile fetching even if "Open each business profile page" is off, and the Actor queues about 3× the candidates to still hit your row target.

## `minRating` (type: `string`):

Which? ratings are out of 5 and vetted traders cluster above 4.5, so this filter only bites at the very top of the scale. Traders with no reviews yet have no rating at all and are removed by any value above 0 — that is about 5% of the directory (rating fill measured 94.9% on 198 rows).

## `minReviewCount` (type: `integer`):

Social-proof floor, applied to reviewCount. Newly endorsed traders often sit at 0, and 0 here is a real zero rather than a missing value. Set 10 for firms with a visible review history.

## `minYearsEndorsed` (type: `integer`):

Which? publishes NO company founding year — the only tenure signal on the site is the "ENDORSED SINCE MMM YYYY" date on the listing card, which this Actor converts to whole years. This is years endorsed by Which?, not years in business. 64% of the 198-row London sample had been endorsed 5+ years, so 3 is a mild filter and 8 is a strong one.

## `fetchProfileDetails` (type: `boolean`):

ON (default) opens every profile and returns the full record — website, street address, county, the complete trade and service lists, areas covered, award badges, opening hours and the description. OFF returns ONLY what the listing card publishes (name, phone, email, postcode, geo, rating, review count, endorsed-since) and never opens a profile: about 12× fewer requests. Measured fill in OFF mode over 117 Manchester rows: phone 100%, email 99.1%, rating 94.9%, postcode 100%, geo 100%. The price per trader is identical either way — this buys speed, not money.

## `includeDescription` (type: `boolean`):

The trader's own "About" text — often 100+ words, which is what makes a CSV heavy. Turn it off for a slim lead file. Costs no extra requests either way.

## `includeServices` (type: `boolean`):

The individual jobs under each trade heading ("Gas safety testing and inspection", "Underfloor heating", "Emergency plumbing services" …). Averages 16.2 per trader on the 198-row London sample, with 97.5% of traders carrying at least one. This is the field to segment on if you sell to a specific job type.

## `includeOpeningHours` (type: `boolean`):

Schema.org opening-hours strings, e.g. "Mo-Fr: 08:00-18:00" or "Mo-Su: 24 hours". Present on 82.8% of the 198-row sample.

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

Stop after this many unique traders. You are billed $0.004 per trader RETURNED, so this is your hard cost ceiling: 100 = $0.40, 1,000 = $4.00, the whole 3,674-business directory = $14.70. Duplicates are dropped before billing and never count against it, and rows your filters remove are never charged.

## `maxPagesPerSearch` (type: `integer`):

How deep to walk each trade + location listing. 12 traders per page, no promoted repeats, so a page adds 12 new traders — 10 pages ≈ 120 traders per trade × town. Listings also end on their own: plumbers-in-london runs out at page 17 with 198 traders, and Which? marks the end with a redirect back to the last page.

## `skip` (type: `integer`):

Resume where the last run stopped instead of re-buying rows you already have: run 1 with maxItems 500, run 2 with skip 500. Applies to the sitemap and search routes in the order the site returns them.

## `maxConcurrency` (type: `integer`):

Kept low on purpose — Which? Trusted Traders is a small site and there is nothing to gain from hammering it. Measured sustained load at concurrency 4 was 215/215 = 100% success through the Apify datacenter proxy. Values above 5 are rejected by the schema.

## `requestDelayMs` (type: `integer`):

Pause each worker waits after every request. The default 500 ms at concurrency 3 works out at roughly 2 requests/second. Which? declares no crawl-delay in robots.txt; this is politeness, not a requirement.

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

Which? is NOT walled. Measured 2026-08-12: direct, Apify datacenter and Apify RESIDENTIAL (with and without country GB) every rung returned HTTP 200 with a full 12-card page — so the default is the CHEAP datacenter rung, which held 100% over a 215-request sustained load, rather than billing you for residential IPs you do not need. If you ever do see failures, switch to RESIDENTIAL + country GB: measured equally reliable (29/30) but slower, 1.63 req/s against 2.22.

## Actor input object example

```json
{
  "source": "search",
  "trades": [
    "plumbers"
  ],
  "locations": [
    "london"
  ],
  "startUrls": [],
  "profileUrls": [],
  "onlyWithPhone": false,
  "onlyWithEmail": false,
  "onlyWithWebsite": false,
  "minRating": "0",
  "minReviewCount": 0,
  "minYearsEndorsed": 0,
  "fetchProfileDetails": true,
  "includeDescription": true,
  "includeServices": true,
  "includeOpeningHours": true,
  "maxItems": 100,
  "maxPagesPerSearch": 10,
  "skip": 0,
  "maxConcurrency": 3,
  "requestDelayMs": 500,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per unique Which?-endorsed business: name, trades and services, areas covered, phone, email, website, street address, postcode, county, latitude/longitude, rating, review count and sub-scores, endorsement date, years endorsed, award badges, opening hours and description. Deduplicated run-wide on the profile URL, so a firm endorsed for several trades is never billed twice.

# 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 = {
    "trades": [
        "plumbers"
    ],
    "locations": [
        "london"
    ],
    "maxItems": 100,
    "maxPagesPerSearch": 10,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/which-trusted-traders-scraper").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 = {
    "trades": ["plumbers"],
    "locations": ["london"],
    "maxItems": 100,
    "maxPagesPerSearch": 10,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/which-trusted-traders-scraper").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 '{
  "trades": [
    "plumbers"
  ],
  "locations": [
    "london"
  ],
  "maxItems": 100,
  "maxPagesPerSearch": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/which-trusted-traders-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/which-trusted-traders-scraper"
        }
    }
}

```

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/fq3SU8VO1qKwLxURs/builds/8umF9UwcKIhiw9m5q/openapi.json
