# UK B2B Leads Verifier (`pradio/b2b-leads`) Actor

Feed a bought list of UK b2b leads as columns or a CSV/JSON URL. Each row is checked against the UK Companies House register plus email, website and phone checks: ok, dissolved, in\_distress, not\_on\_register or unverifiable, with company number, address and register link. Unjudged rows are free.

- **URL**: https://apify.com/pradio/b2b-leads.md
- **Developed by:** [Pradio Actors](https://apify.com/pradio) (community)
- **Categories:** Lead generation, Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.70 / 1,000 row judgeds

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 B2B Leads Verifier

### What does UK B2B Leads Verifier do?

UK B2B Leads Verifier checks a bought UK lead list against the Companies House register and returns one verdict row per lead. Companies House is the UK's public register of companies. A lead that claims a UK company is matched on its name, number or website stem; its email domain, website and phone are checked alongside. Each judged row carries 26 fields: the verdict, a match score, the matched company's number, status, registered address and dates, and the register page to open as proof. Paste the list in as columns or point it at a CSV or JSON file, then press Start.

On a measured run of 100 leads, all 100 came back judged: 79 verified `ok`, 15 read `dissolved`, 4 `in_distress` and 2 `not_on_register`. A judged row costs $0.005.

### Who uses UK B2B Leads Verifier

| Who | What they run it for |
|---|---|
| A founder or sales-ops lead who just paid for a list | Check every row before the first send instead of spot-checking a sample by hand. |
| An outbound team about to spend sender reputation | Suppress dissolved and in-distress companies before the campaign, not after the bounces. |
| A buyer disputing a bad list | The dead rows come back with the reason and the register link, ready to take to the seller for a refund. |
| A calling team working a phone column | Valid numbers come back with a line type; invalid ones are flagged before anyone dials. |

### Features

- **Register verdict on every UK-signalled lead.** The claimed company name, company number or website stem is matched against Companies House. A UK signal is a company number, a +44 phone, a .uk domain or a UK legal form such as ltd or plc in the name. A row carrying no UK signal is never searched, so a non-UK company can never collect a false UK verdict.
- **Four checks per lead, none behind a paywall.** Register status, MX records on the email domain, a live-website probe and a phone-number parse with line type. No paid lookups and no API keys.
- **Columns or a file.** Paste `emails`, `urls`, `companies` and `phones` as parallel columns, or set `leadListUrl` to a CSV or JSON list. An Apify dataset URL from a lead-finder run works directly.
- **No login, no browser, no key.** Every check is a plain logged-out GET or a DNS lookup, one request per lead. The register's developer API serves 0 requests a day without a key; the public search this Actor reads needs none.
- **Polite register reads.** Lookups run one at a time with a pause between them, under the register's 120-requests-a-minute guideline. A refusal is backed off once, not hammered.
- **Misses are free and named.** A failed register read, a block, a timeout or a row with nothing to judge is pushed uncharged, with the reason on it.

### What you can count on

- You pay only for rows the run judged. A row it could not judge is pushed as an uncharged `ITEM_STATUS` row, so you see the miss and are never billed for it.
- Every row is charged only after it is written to your dataset; a row you cannot see is never billed.
- A run that finds nothing returns one `NOTHING_TO_JUDGE` row that says so, never an empty dataset.
- A spending limit stops the run cleanly with a `STOPPED_EARLY` row saying how many rows were returned and how many were not.
- Every run writes a `RUN_SUMMARY` entry with rowsFetched, rowsPushed, rowsCharged and duplicatesDropped, so a short run and a broken one are told apart.
- If the register changes what it answers, the run fails with the error in the log. It never returns rows full of nulls and calls it success.
- No value is invented: a field the register does not show is null, and this page says which.

### Why this one

- The most-used alternative on this platform, run on its own default board on 2026-09-15, returned 1 row where this Actor returned 4 on the same input. Its row left every field it declares empty: the company, the name, the email, the phone, the source.
- No start fee of ours. Start fees across the Store run from $0.00001 to $0.10 before the first row comes back. This Actor declares no start event; only Apify's standard platform start charge of $0.00005 applies, once per run.
- A miss is free and explained. The row still lands in your dataset, uncharged, with the failure named on it.
- The judged rate is measured, not promised: a list of 123 leads the product had never seen came back 122 of 123 judged, every judged one `ok`, the one it could not read written uncharged, worked in runs under the 100-lead cap, read without a browser.
- Every matched row carries `evidence_url`, the live register page behind the verdict. It filled on 98 of the 100 judged rows in the measured run.

### What data does UK B2B Leads Verifier return?

One row per lead. A real `dissolved` verdict from a run looks like this:

```json
{
  "email": "info@bae.co.uk",
  "domain": "bae.co.uk",
  "status": "dissolved",
  "match_score": 80,
  "reason": "SOCIETY OF BRITISH AEROSPACE COMPANIES (THE) (00143447): dissolved on Companies House; the email domain accepts mail; the website answers; the phone number is valid (fixed_line)",
  "error": null,
  "found": true,
  "is_active": false,
  "claimed_company": "british aerospace",
  "matched_name": "SOCIETY OF BRITISH AEROSPACE COMPANIES (THE)",
  "normalised_status": "dissolved",
  "status_as_of": "2026-09-18T02:55:41.988Z",
  "phone_number_status": "valid",
  "phone_line_type": "fixed_line",
  "website_answers": true,
  "email_domain_accepts_mail": true,
  "dead_reason": "dissolved on the register 2023-01-17",
  "evidence_url": "https://find-and-update.company-information.service.gov.uk/company/00143447",
  "company_status": "dissolved",
  "company_number": "00143447",
  "company_type": "private-unlimited",
  "date_of_creation": "1916-03-29",
  "date_of_cessation": "2023-01-17",
  "address": {
    "locality": "London",
    "postal_code": "SE1 7SP",
    "address_line_1": "9 Albert Embankment",
    "premises": "Salamanca Square"
  },
  "register_description": "00143447 - Dissolved on 17 January 2023",
  "row_type": "ROW"
}
```

#### The verdict vocabulary

`status` carries one of five charged verdicts, or a miss word on an uncharged row:

| `status` | What it means |
|---|---|
| `ok` | Matched on the register and active there. On a weaker match (`match_score` under 90) the verdict is about the entity `matched_name` names, so check it is yours. |
| `dissolved` | Matched on the register; the entry shows it dissolved, by its status word or, when the entry carries no status word, by a closed company type such as `converted-or-closed` named on `dead_reason`. |
| `in_distress` | Matched; the register shows liquidation, administration, receivership or similar. |
| `not_on_register` | The UK register was searched and no entry matched the claim. It means "no UK company matched", not "the company does not exist". |
| `unverifiable` | Judged, but the register could not confirm the row: the lead carried no UK signal to search on, the register was down and never asked for this row, or the matched entry carried no status the run could classify. Its `match_score` is null. A row whose own register read was attempted and failed is a miss, not this verdict, and is free. |
| `fetch_failed`, `blocked`, `timeout`, `aborted`, `not_found`, `error` | The run could not judge this row. Pushed so you see it, never billed. |

Two words are easy to confuse and worth a second look. `not_on_register` is a verdict: the register was searched and nothing matched. `not_found` is a miss: the row carried no email, website, company or phone to judge, and it is free.

#### Every field, in order

| Field | What it carries |
|---|---|
| `email` | The email the lead row claimed, echoed back so the verdict lines up with your list. Null when the list carried no email column. |
| `domain` | The domain the lead was judged on, taken from its website or else from its email address. |
| `status` | The verdict, or the miss word on an uncharged row. |
| `match_score` | Register-match confidence: how strongly the register answer supports the verdict, 0 to 100. A score of 95 or more is the claim itself, name or number. Under 90 the match is weaker: the register name extends the lead's name, shares only part of its words, or the row's own geography says it may be a different company. `matched_name` and `evidence_url` name and link the entity the verdict is actually about on those rows. A fixed middling value on `not_on_register`; null on `unverifiable`, where a number would claim confidence the register never gave. |
| `reason` | One sentence naming the matched company and what each check found, readable without opening the register. |
| `error` | What failed, on an uncharged miss row only. Null on every judged row. |
| `found` | True when a register entry matched the claim. |
| `is_active` | True when the matched company is active on the register right now. |
| `row_type` | `ROW` for a judged lead, `ITEM_STATUS` for an uncharged miss, `NOTHING_TO_JUDGE` or `STOPPED_EARLY` on a status row. |
| `register_description` | The register's own one-line description of the company, verbatim: such as "Incorporated on 27 November 1947", truncations included. |
| `address` | The registered office address the register publishes, as an object of region, locality, postal code and address lines. |
| `company_type` | The register's type word for the company, verbatim: `ltd`, `plc`, `converted-or-closed` and the like. |
| `company_status` | The register's own status word: `active`, `liquidation`, `dissolved` and so on. |
| `date_of_creation` | The incorporation date the register shows. |
| `company_number` | The register's permanent number for the matched company. |
| `date_of_cessation` | The dissolution date, when the register's entry names one. A dissolved row whose entry carries no date stays null. |
| `claimed_company` | The company your lead row claimed, echoed from the input: a name or a UK company number. |
| `matched_name` | The register's legal title for the entry it matched, so a trading name still lands on the right company. |
| `normalised_status` | The normalised register read: `active`, `dissolved`, `in_distress`, `no_match`, `unchecked` when the register's answer could not be read or classified, or `not_applicable` when the row carried no UK signal and the register was not asked. |
| `status_as_of` | When the verdict was computed, ISO-8601. |
| `phone_number_status` | `valid` or `invalid` as a number. Whether a line rings is not claimed. |
| `phone_line_type` | The line type where the number parses: `mobile`, `fixed_line`, `toll_free`, `voip` and similar. |
| `website_answers` | Whether the website answers HTTP at all. A 403 still counts as an answer; false means nothing answered. |
| `email_domain_accepts_mail` | Whether the email's domain publishes MX records. The mailbox itself is never probed. |
| `dead_reason` | On a `dissolved` or `not_on_register` verdict, which check killed the row, with the register date when there is one. An `in_distress` company is still live and an `unverifiable` one was never confirmed, so they carry none. |
| `evidence_url` | The register page to open to see the evidence behind the verdict. |

Status rows carry four fields of their own: `reason` says why the run ended that way, and `rowsFetched`, `rowsReturned` and `rowsRemaining` give the counts.

On the measured run's 100 judged rows the register fields filled nearly throughout. `matched_name`, `company_number`, `company_type`, `register_description` and `evidence_url` filled on 98 of 100, `company_status` on 97, and `address` and `date_of_creation` on 95. `claimed_company`, `domain`, `status`, `match_score`, `reason`, `found`, `is_active`, `normalised_status` and `status_as_of` filled on all 100. `date_of_cessation` filled on 14 of the 15 dissolved rows, and `dead_reason` on 17 (the 15 `dissolved` and 2 `not_on_register` rows).

One field stays null on every judged row by design, not by accident:

| Field | Why it stays null |
|---|---|
| `error` | It fills only on an uncharged miss row, where it says what failed. A judged row has nothing to report. |

The Console's Overview view hides `error`; the All fields view and every export carry it.

### How much does it cost?

The price is $0.005 per judged row, billed as the `row-judged` event, and only after the row is written to your dataset. A dead lead is still a judged row: a `dissolved` or `not_on_register` verdict is the answer you paid for, and an `unverifiable` row was judged too. Miss rows, status rows and dropped duplicates are never billed.

| Leads in the list | If every lead judges |
|---|---|
| 100 | $0.50 |
| 1,000 | $5.00 |
| 10,000 | $50.00 |

The coverage figure is measured on a different list: a set of 123 leads the product had never seen came back 122 of 123 judged, worked in runs under the 100-lead cap, with no browser. The one row it could not read was written uncharged, which is the only way a run bills under the ceiling. The 100-lead run this page cites elsewhere is a different measurement. A run judges at most 100 leads, so longer lists are worked in runs of 100. Apify also bills its standard `apify-actor-start` charge at $0.00005 per start event, one event per GB of run memory with a minimum of one; at this Actor's 256 MB default that is one event per run. This Actor adds no start fee of its own.

### How do I use UK B2B Leads Verifier?

1. Open the Actor page and press Start.
2. Feed it leads one of two ways. Paste the `emails`, `urls`, `companies` and `phones` columns, keeping the rows parallel so position 1 in each array is the same lead. Or set `leadListUrl` to a CSV or JSON list, including a dataset URL from a lead-finder run.
3. Leave `maxItems` at its default of 100, which is also the cap, or set a lower number of rows the run may return.
4. Press Start. Verdict rows land in the dataset as they are judged.

Example input, the same four leads the input form ships with:

```json
{
  "emails": ["press.office@tesco.com", "info@carillion.co.uk", "info@maplin.co.uk", "sales@zzqx-nonexistent-example.com"],
  "urls": ["https://www.tesco.com", "https://www.carillion.co.uk", "https://www.maplin.co.uk", "https://zzqx-nonexistent-example.com"],
  "companies": ["TESCO PLC", "CARILLION PLC", "04220419", "ZZQX EXAMPLE NONEXISTENT LIMITED"],
  "phones": ["+44 1992 632222", "+44 20 7946 0958", "", ""],
  "maxItems": 100
}
```

The same run through the Apify API is one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/Pradio~b2b-leads/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["info@carillion.co.uk"], "companies": ["CARILLION PLC"]}'
```

### Input

| Input | Type | Default | What it does |
|---|---|---|---|
| `emails` | array | four example rows | The email each lead row claims. Its domain is checked for MX records; the mailbox is never probed. |
| `urls` | array | four example rows | The website each lead claims, fetched once to see whether it answers. Its domain also helps match the company on the register. |
| `companies` | array | four example rows | The company name or UK company number each row claims. A registered name or a number matches sharpest. |
| `phones` | array | four example rows | The phone number each row claims, checked for validity and line type. |
| `leadListUrl` | string | none | A CSV or JSON lead list read instead of, or as well as, the columns above. |
| `maxItems` | integer | 100 | The most rows one run returns, capped at 100. Leads beyond the cap are not judged. |

#### The columns are parallel

`emails[i]`, `urls[i]`, `companies[i]` and `phones[i]` describe one lead. Any subset works. A lead with only a domain is still matched by the domain's name stem. A lead with only a phone still gets its number checked. A row with nothing usable comes back as a `not_found` miss, free.

#### leadListUrl

Point it at a CSV file or a JSON array of objects; an Apify dataset-items URL from a lead-finder run works directly. CSV headers are matched by the names list sellers actually use. The email column answers to `email`, `e-mail` or `mail`. The website answers to `website`, `url` or `domain`. The company answers to `company`, `organisation` or `business`. The phone answers to `phone`, `telephone` or `mobile`. Only those four columns are read. A contact's name, a job title or any other column in the file is never copied into a row. A list URL that answers with an error fails the run instead of returning an empty dataset.

### Output

A run ends one of four ways, and `row_type` says which:

| `row_type` | What it means |
|---|---|
| `ROW` | A judged lead. Charged. |
| `ITEM_STATUS` | A miss the run could not judge. Pushed so you see it, never billed. |
| `NOTHING_TO_JUDGE` | The input gave the run no leads: empty columns and no `leadListUrl`, or a file whose rows carried none of the four columns. One row, uncharged. A zero result is an answer, not silence. |
| `STOPPED_EARLY` | The charge limit was reached before every fetched row was pushed. The row carries `rowsFetched`, `rowsReturned` and `rowsRemaining`, so finishing and being cut short never produce the same dataset. |

Every run also writes a `RUN_SUMMARY` entry to the run's key-value store: `rowsFetched`, `rowsPushed`, `rowsCharged`, `rowsUncharged`, `duplicatesDropped`, `startedAt` and `finishedAt`. It is the fast way to tell a short list from a broken run. When `maxItems` cuts a run short, `rowsFetched` against `rowsPushed` there shows what the list held against what came back.

### What can you do with the data?

**Take the dead rows back for a refund**. Filter `status` to `dissolved`, `in_distress` and `not_on_register`, and `dead_reason` plus `evidence_url` itemise exactly which leads failed and on what evidence. On the measured 100-lead run that meant 15 dissolved rows, 4 in distress and 2 with no register match. That is the report a money-back guarantee asks for.

**Clean a list before the first send**. Suppress every row that is not `ok`, then read `website_answers`, `email_domain_accepts_mail` and `phone_number_status` on the survivors to drop any whose checks you would not send to. `ok` means the company is live on the register; the check fields are the deliverability read. 79 of the 100 measured leads verified active. The rest would have cost bounces, caller hours and sender reputation to discover the slow way.

**Re-verify an ageing list**. Companies dissolve between the day a list is bought and the day it is worked; 15% of the measured list already had. Upload last quarter's extract again and `status_as_of` dates every verdict, so a stale row is caught before it is sent.

**Triage a dialling list**. `phone_number_status` drops numbers that do not parse. `phone_line_type` splits mobiles from fixed lines and VoIP, so the team dials the rows worth dialling first.

### Use UK B2B Leads Verifier with AI agents

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=Pradio/b2b-leads"
```

Paste that line into your agent's setup and it can start runs and read verdict rows as tools.

### Personal data

**Attribution**. The register evidence on every row is public sector information from Companies House. *Contains public sector information licensed under the Open Government Licence v3.0*, at https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/.

**Transparency**. Verdicts derive from the public Companies House register and from DNS and HTTP checks on the details each lead claims. The basis for processing is legitimate interests: checking that business data sold as accurate is accurate. A data subject may object and request deletion of their particulars.

**What a row carries**. A verdict row carries the verdict and the matched company particulars it needs: company name, number, status and registered office address. It does not echo officers' dates of birth or home addresses. Officer or PSC names are not read at all; the register search returns company records only. Of the list you upload, only the email, website, company and phone columns are read. A person's name or any other column never reaches a row.

**How the register is read**. Logged-out plain GETs, no credential and no bypass, one page per request, with `maxItems` capped at 100 rows a run. A refused or failed read fails the run; it does not retry past a block.

**Controller**. You choose which list to upload and why, so the controller for the personal data in it is you or your organisation. The register data is published for public use under the licence above.

### Release notes

- `0.1`: first public build, a lead-list verifier on the Companies House register. Per-lead verdicts (ok, dissolved, in\_distress, not\_on\_register, unverifiable) with the matched company's particulars and evidence link, MX, website and phone checks on the row, and a 100-lead cap per run.

### Limits

- **UK register only, and it only asks about UK-signalled leads**. The register check is Companies House. A lead carrying a UK signal is searched. The signal is a company number, a +44 phone, a .uk domain or a UK legal form in the name. One that matches nothing reads `not_on_register`, which means "no UK company matched", not "the company does not exist". A lead with no UK signal is never asked and reads `unverifiable` with `normalised_status` `not_applicable`. A non-UK row can still match a same-named UK company, since overseas groups do register UK subsidiaries and branches. When the row's own domain or phone contradicts the UK claim, the match stands. Its confidence is capped, and `reason` says it may be a different company.
- **100 leads a run**. `maxItems` is capped at 100 and the run judges no more than that. A longer list is worked in runs of 100, each billed for its own judged rows.
- **The mailbox itself is never probed**. `email_domain_accepts_mail` is an MX check on the domain, so a full or dead inbox still reads true.
- **"Valid" stops at the number**. `phone_number_status` and `phone_line_type` come from parsing the number itself. Whether the line rings is not claimed, and no carrier lookup is made.
- **"Answers" is generous on purpose**. `website_answers` is true when anything answers HTTP, even a 403 or a parked page. False means nothing answered at all.
- **Free-mailbox emails never drive a match**. A lead whose only company hint is a gmail address is never guessed at the register from the word "gmail".
- **One refusal is backed off, not hammered**. A 403 or 429 from the register pauses the run once. A second refusal marks the register down. The hit row is pushed free as `blocked`. Later rows are judged on the other checks with `normalised_status` set to `unchecked`.
- **Reads are sequential**. Register calls run one at a time with a pause between them, under the register's 120-a-minute guideline, so a large list takes a while. That is the polite speed, not a fault.
- **Errors run towards naming a miss, never towards a fake verdict**. A check that fails says so on the row rather than guessing. A field the register does not publish is null rather than filled.

### Troubleshooting

**I uploaded 50 leads but only 47 rows came back. Where are the rest?**
Two places to look. Rows sharing an email are de-duplicated before anything is billed, so repeats collapse to one row each. And `maxItems` caps how many rows are pushed. `RUN_SUMMARY` splits it exactly: `rowsFetched`, `rowsPushed`, `duplicatesDropped`.

**The dataset has a single row and it is not a verdict. Is the run broken?**
No. That is a `NOTHING_TO_JUDGE` status row: the input gave it no leads. Check the columns are parallel arrays, or that `leadListUrl` answered and its headers were recognised.

**A row says `unverifiable` with `normalised_status` `not_applicable`, but the company is real.**
The row carried no UK signal: no company number, +44 phone, .uk domain or UK legal form in the name. The UK register was not asked. The checks that did run (email domain, website, phone) are on the row. If the company is UK, give it a .uk website, a +44 number or its Companies House name and it will be searched.

**A row says `unverifiable` with `normalised_status` `unchecked`, but the company is real.**
Either the register could not be read for that row, usually after it refused an earlier request and the run stopped asking, or the register answered with an entry whose status the run could not classify; `company_status` then carries the register's own word. The other checks still ran and are on the row. Re-run the list once the register is answering again.

**The run ended with a `STOPPED_EARLY` row.**
Your charge limit was reached. The status row carries `rowsReturned` and `rowsRemaining`; raise the limit and re-run to see the rest. Rows already returned stay billed, and nothing after them is.

Something looks wrong? Report a problem through the Issues tab on the Actor's Store page and it gets looked at.

### FAQ

**Can I use integrations with UK B2B Leads Verifier?**
Yes. The dataset exports as JSON, CSV, Excel and more, so a dead-row report drops straight into a spreadsheet or a CRM import. Apify integrations such as Zapier and Make can start a run when a new list lands.

**Can I use UK B2B Leads Verifier with the Apify API?**
Yes. POST the same JSON the input form takes to the Actor's run endpoint; the curl in the how-to section is the whole call. Runs, datasets and schedules are all reachable through the standard API and client libraries.

**Can I use UK B2B Leads Verifier through an MCP server?**
Yes. The snippet in the AI-agents section registers this Actor with a compatible agent, which can then start runs and read the verdict rows.

**Is it legal to check a lead list this way?**
The register half of each verdict is Companies House's public company search, read with a plain logged-out GET. That is public sector information licensed under the Open Government Licence v3.0, with no login and no bypass. The input side is a list you already hold. What you may do with it sits between you and its seller.

### Not affiliated

UK B2B Leads Verifier is an independent tool. It is not affiliated with, endorsed by or connected to Companies House, Apify or any lead-list provider. Company data comes from the public register under the Open Government Licence v3.0.

# Actor input Schema

## `emails` (type: `array`):

The email column of the lead list. One entry per lead, kept parallel with the other columns: position 1 in each array is the same lead. The domain is checked for MX records; the mailbox itself is never probed.

## `urls` (type: `array`):

The website column of the lead list, one URL per lead. Each is fetched once to see whether the site still answers, and its domain helps match the company on the register.

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

The most rows one run returns, capped at 100. Leads beyond the cap are not judged.

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

The company column of the lead list: the name or UK company number each lead claims. Matched against the UK Companies House register when the row carries a UK signal: a company number, a +44 phone, a .uk domain or a UK legal form in the name.

## `phones` (type: `array`):

The phone column of the lead list, one number per lead. Each is parsed for validity and line type; whether the line rings is not checked.

## `leadListUrl` (type: `string`):

A URL to a CSV or JSON lead list to read instead of the columns above, including an Apify dataset URL from a lead-finder run. Column names such as email, url, company and phone are recognised.

## Actor input object example

```json
{
  "emails": [
    "press.office@tesco.com",
    "info@carillion.co.uk",
    "info@maplin.co.uk",
    "sales@zzqx-nonexistent-example.com"
  ],
  "urls": [
    "https://www.tesco.com",
    "https://www.carillion.co.uk",
    "https://www.maplin.co.uk",
    "https://zzqx-nonexistent-example.com"
  ],
  "maxItems": 100,
  "companies": [
    "TESCO PLC",
    "CARILLION PLC",
    "04220419",
    "ZZQX EXAMPLE NONEXISTENT LIMITED"
  ],
  "phones": [
    "+44 1992 632222",
    "+44 20 7946 0958",
    "",
    ""
  ]
}
```

# Actor output Schema

## `rows` (type: `string`):

The verifier rows for this run: one verdict per lead, plus a status row when the run ended early or had nothing to judge.

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

Counts for this run: leads fetched, rows pushed, rows charged, duplicates dropped, stopped early.

# 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 = {
    "emails": [
        "press.office@tesco.com",
        "info@carillion.co.uk",
        "info@maplin.co.uk",
        "sales@zzqx-nonexistent-example.com"
    ],
    "urls": [
        "https://www.tesco.com",
        "https://www.carillion.co.uk",
        "https://www.maplin.co.uk",
        "https://zzqx-nonexistent-example.com"
    ],
    "companies": [
        "TESCO PLC",
        "CARILLION PLC",
        "04220419",
        "ZZQX EXAMPLE NONEXISTENT LIMITED"
    ],
    "phones": [
        "+44 1992 632222",
        "+44 20 7946 0958",
        "",
        ""
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pradio/b2b-leads").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 = {
    "emails": [
        "press.office@tesco.com",
        "info@carillion.co.uk",
        "info@maplin.co.uk",
        "sales@zzqx-nonexistent-example.com",
    ],
    "urls": [
        "https://www.tesco.com",
        "https://www.carillion.co.uk",
        "https://www.maplin.co.uk",
        "https://zzqx-nonexistent-example.com",
    ],
    "companies": [
        "TESCO PLC",
        "CARILLION PLC",
        "04220419",
        "ZZQX EXAMPLE NONEXISTENT LIMITED",
    ],
    "phones": [
        "+44 1992 632222",
        "+44 20 7946 0958",
        "",
        "",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("pradio/b2b-leads").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 '{
  "emails": [
    "press.office@tesco.com",
    "info@carillion.co.uk",
    "info@maplin.co.uk",
    "sales@zzqx-nonexistent-example.com"
  ],
  "urls": [
    "https://www.tesco.com",
    "https://www.carillion.co.uk",
    "https://www.maplin.co.uk",
    "https://zzqx-nonexistent-example.com"
  ],
  "companies": [
    "TESCO PLC",
    "CARILLION PLC",
    "04220419",
    "ZZQX EXAMPLE NONEXISTENT LIMITED"
  ],
  "phones": [
    "+44 1992 632222",
    "+44 20 7946 0958",
    "",
    ""
  ]
}' |
apify call pradio/b2b-leads --silent --output-dataset

```

## MCP server setup

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

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/sRTM6HQgZ8vWfpc8w/builds/5papiIf3AJqef7kGM/openapi.json
