# Local Business Leads Enrichment & Website Audit (`truswen/local-business-leads-website-audit`) Actor

Turn Google Maps results or website lists into qualified agency leads: contacts, the owner named on the site, tech stack, a 28-point website audit and per-service opportunity scores with factual pitch lines.

- **URL**: https://apify.com/truswen/local-business-leads-website-audit.md
- **Developed by:** [Benjamin Zsigri](https://apify.com/truswen) (community)
- **Categories:** Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$15.00 / 1,000 business auditeds

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

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Local Business Leads Enrichment & Website Audit

Feed it a Google Maps Scraper run (or a list of websites). Get back one lead row per business: the contacts and the owner or manager named on the website, the tools the site runs, a **28-point website audit**, and an **opportunity score for each service an agency sells**, with short factual pitch lines you can quote in your first message.

Built for web design, SEO, ads, booking, chat, email and reputation agencies that prospect local businesses: hair and beauty salons, dentists and clinics, restaurants, trades, gyms, studios.

### What one row tells you

| Column | Example |
|---|---|
| `business_name`, `category`, `city` | `Bella Hair Studio`, `Hair salon`, `Leeds` |
| `google_rating`, `google_reviews` | `3.9`, `12` (from the Maps item) |
| `decision_maker_name`, `decision_maker_role`, `decision_maker_email` | `Sarah Jones`, `Owner & Senior Stylist`, `null` |
| `primary_email`, `primary_phone`, `facebook`, `instagram`, `linkedin` | from the website |
| `audit_score` | `15` (0 to 100) |
| `issues` | `["https", "mobile_viewport", "online_booking", ...]` |
| `top_opportunity`, `top_opportunity_score` | `lead_capture`, `100` |
| `opportunity_website` ... `opportunity_ai_search` | one 0 to 100 score per service |
| `top_pitch_hook`, `pitch_hooks` | `No online booking tool was detected on the 3 pages read.` |
| `website_platform`, `analytics_tools`, `ad_pixels`, `booking_tool`, `chat_tool` | `WordPress`, `[]`, `[]`, `null`, `null` |
| `email_provider`, `dmarc_policy` | `Google Workspace`, `missing` |

The full row adds every audit check with its observed value (`audit_checks`), every opportunity with the findings behind its score (`opportunities`), all decision makers with their source page, all emails and phones with their source page, every detected technology with its evidence, the DNS footprint and the original Maps item. The example above is a fictional business.

### The audit

Each check answers `pass`, `warn`, `fail`, or `unknown` when the data was not available. Nothing is guessed: a check the crawl could not answer stays `unknown` and does not count in the score.

| Area | Checks |
|---|---|
| Website | HTTPS, mobile viewport, homepage response time, homepage HTML size, copyright year, WordPress version, mixed content |
| Tracking | analytics or tag manager, advertising pixel (Meta, Google Ads, TikTok and others) |
| Conversion | online booking tool (for businesses that take appointments), live chat or messaging, contact form, tap-to-call phone link |
| Search | LocalBusiness structured data, title and meta description, H1, image alt texts, social preview image, XML sitemap |
| AI search | AI search crawlers (OAI-SearchBot, ChatGPT-User, PerplexityBot, Claude-SearchBot) and training crawlers (GPTBot, ClaudeBot, Google-Extended, CCBot) in robots.txt, llms.txt |
| Email | SPF, DMARC policy, published email on the business domain or on a free mail provider |
| Reputation | Google rating and review count (from the Maps item), review widget on the site, links to social profiles |

Online booking is judged only for businesses that take appointments or reservations, recognised from the Google Maps category or the site's structured data (salons, dentists, clinics, therapists, gyms, restaurants and similar, in English and the main European languages). A plumber is not pitched a booking tool.

### Opportunity scores and pitch lines

Every service gets a score from 0 to 100: the sum of points for the checks that service fixes, full points for a fail and half for a warning, capped at 100. Each point comes with the finding that earned it, so you can see why a lead scored the way it did. Scores rank the leads of one run against each other; they are not a revenue forecast.

| Service | Driven by |
|---|---|
| `website` | HTTPS, mobile viewport, response time, copyright year, CMS version, mixed content; 100 when the listing has no website |
| `local_seo` | LocalBusiness schema, title and meta description, review count, H1, sitemap, rating, alt texts |
| `ads_tracking` | no analytics, no advertising pixel |
| `online_booking` | no booking tool at a business that takes appointments |
| `lead_capture` | no live chat, no contact form, no tap-to-call link, free-mail address |
| `email_deliverability` | DMARC, SPF, free-mail address |
| `reputation` | Google rating, review count, no review widget |
| `ai_search` | AI crawlers blocked, no LocalBusiness schema, no llms.txt, title and meta, sitemap |

`pitch_hooks` holds up to five distinct sentences, strongest first. Set **Your service** in the input and the findings for that service come first. The sentences state what the crawl observed ("No contact form was found on the 4 pages read."), so they hold up when the business owner checks.

### Input

- **Dataset import**: the dataset ID of a Google Maps Scraper run. The `website` field is audited; the name, category, rating, review count, phone, address and place ID are copied in. A Google Maps link is never mistaken for the website.
- **Listings without a website** become free rows with status `no_website` and a website opportunity of 100 (switch this off if you only want audited sites). A business whose "website" is only a Facebook or Instagram page or a marketplace listing (Fresha, Booksy, Treatwell, Yelp and similar) gets a free `no_own_website` row the same way.
- **Websites**: a plain list works too, with or without a dataset.
- **Your service**, **email checks in DNS**, **pages per website** (4 by default) and an optional proxy.

### Where your list can come from

- **Google Maps Scraper**: set `datasetId` to its run's dataset. This Actor never searches Google Maps itself; it works on the dataset you bring.
- **A CSV file or spreadsheet**: paste the website column into `websites`, one per line.
- **Your CRM**: export the accounts and paste the website column, or push the rows into an Apify dataset with the API and set `datasetId` to it. `datasetUrlField` names the column when it is not one of the usual names.
- **Make, n8n, Zapier or Clay**: start the Actor through the Apify API (Apify also offers ready-made modules for some of these tools), pass `websites` in the input and read the run's dataset as the output.

### Pricing

Pay per event, shown on the Actor's pricing tab:

- `business-audited`: one per business whose website answered and was audited.

Rows for listings without a website (or with only a social or marketplace page), and for websites that could not be read (blocked by robots.txt, offline, refusing automated requests), are free and carry the reason. When your maximum cost per run is reached, the run stops cleanly before the next website.

### Access rules

- Every request is checked against the site's robots.txt first, including the well-known files (`/sitemap.xml`, `/llms.txt`). If a robots.txt cannot be read, the site is skipped, not worked around. Login pages, CAPTCHAs and other access controls are never bypassed.
- Each website is read at a polite rate, a few pages only, with one identifiable user agent.
- Directory and social platform pages (Google Maps, Facebook, Yelp and the like) are never crawled as a business website.
- Emails hidden by Cloudflare protection are flagged, not decoded.
- A site whose HTTPS connection fails (for example an expired certificate) is read once over plain HTTP and reported as not served over HTTPS.

### Personal data

Decision makers are taken only from the business's own website, with **name, role and the email address the site publishes for them**. Nothing is looked up elsewhere and nothing is guessed. You are responsible for having a lawful basis (for example legitimate interest in B2B prospecting), for honouring objections, and for following the email and calling rules of the countries you contact.

### Known limits

- Websites that render their content only with JavaScript expose few links and little text to the crawler; tools loaded only by a tag manager after the page starts may not be detected.
- The response time is measured from the crawler's server, not from a visitor's phone; treat it as a signal, not a lab measurement.
- "Not detected" means not found on the pages read: a booking tool on a page the crawl did not reach, or one added only after a click, is not seen.
- Google rating and review count come from the input dataset; a plain website list has no reputation data.

### Typical uses

- Turn a Google Maps search ("dentists in Manchester") into a ranked call list for one service.
- Find the businesses without a website, without HTTPS or without a mobile layout.
- Find appointment businesses without online booking or live chat.
- Find domains without DMARC for an email deliverability offer.
- Personalise the first line of an outreach message with a finding the owner can verify.

### Need it done for you?

Want this connected to your CRM or workflow, or adapted to your market? Tribloc, the team behind this Actor, builds these setups. Get in touch at [tribloc.co.uk](https://tribloc.co.uk/).

# Actor input Schema

## `datasetId` (type: `string`):

ID of a dataset with one business per item, for example the output of a Google Maps Scraper run. The website field is read for the audit; the business name, category, Google rating, review count, phone and address are copied into the row. Listings without a website become free rows marked `no_website`. Pick the dataset (or paste its ID); the Actor is given read access to that one dataset only.

## `websites` (type: `array`):

One per line, alternatively or in addition to the dataset. A bare domain (example.com) works. Each website is processed and charged once, even if it appears several times.

## `datasetUrlField` (type: `string`):

Name of the field that holds the website. Leave empty to try the usual names: website, websiteUrl, url, domain, companyWebsite, homepage. A Google Maps listing link is never taken for the website.

## `includeBusinessesWithoutWebsite` (type: `boolean`):

Add a free row for every dataset item that has no website (status `no_website`, website opportunity 100). Turn off to get audited websites only.

## `focusService` (type: `string`):

The findings for this service come first in `pitch_hooks`. Every service is scored either way.

## `checkDns` (type: `boolean`):

Read the domain's public MX, SPF and DMARC records for the email deliverability checks.

## `maxPagesPerWebsite` (type: `integer`):

Homepage included. The crawler reads contact, team and about pages first, where the contacts and the owner's name usually are. 4 is enough for most local businesses.

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

How many websites are processed at the same time. Each website is always requested at its own polite rate.

## `defaultCountry` (type: `string`):

Two-letter code (US, GB, DE ...) used to read local phone numbers when neither the dataset nor the domain tells the country.

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

Optional. Use it only if many websites come back as blocked or with HTTP errors.

## Actor input object example

```json
{
  "websites": [
    "https://www.toniandguy.com",
    "https://www.mydentist.co.uk"
  ],
  "includeBusinessesWithoutWebsite": true,
  "checkDns": true,
  "maxPagesPerWebsite": 4,
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

One row per business: audit score, best opportunity, the first pitch line, decision maker and contacts.

## `results` (type: `string`):

Complete rows: every audit check with its evidence, every opportunity with its reasons, technologies, contacts and the DNS footprint.

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

Businesses by status and charged events.

# 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 = {
    "websites": [
        "https://www.toniandguy.com",
        "https://www.mydentist.co.uk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("truswen/local-business-leads-website-audit").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 = { "websites": [
        "https://www.toniandguy.com",
        "https://www.mydentist.co.uk",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("truswen/local-business-leads-website-audit").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 '{
  "websites": [
    "https://www.toniandguy.com",
    "https://www.mydentist.co.uk"
  ]
}' |
apify call truswen/local-business-leads-website-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,truswen/local-business-leads-website-audit"
        }
    }
}
```

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/FWqyhkntQTsGhULhR/builds/rKNnmScc6bS7dpRLP/openapi.json
