# Website Audit — In Plain English, Not Scores (`alaudinburki/small-business-website-audit`) Actor

Audit any list of websites and get findings a business owner actually understands: what is wrong, what it costs them, and the exact fix. Every row ships a paste-ready summary for a client email. No browser, no API key, no anti-bot.

- **URL**: https://apify.com/alaudinburki/small-business-website-audit.md
- **Developed by:** [alaudin burki](https://apify.com/alaudinburki) (community)
- **Categories:** SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

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

Learn more: https://docs.apify.com/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

## Website Audit — In Plain English, Not Scores

Every other site auditor hands you a number and a list of tag names. This one hands you
**three sentences per problem**: what is wrong, what it is costing the business, and the exact
fix — written the way you would say it to the owner.

Feed it a client list. Get back a column you can paste straight into an email.

### Why this exists

The findings are not generated from a template. They come from a working agency's own
prospecting tool, where the audit's only job is *"having something specific and true to say to a
stranger about their own business."* The checks are ordinary. The wording is the product.

```
No phone number, email or contact form on the page. A visitor ready to buy has no way to
say so. This is the single most expensive omission a small business site can have.
Fix: Put a phone number in the header. Not on a separate contact page - in the header.
```

That is one row of the `emailSummary` field. Fourteen checks, all of them worded like that.

### What you get

| Field | Description |
|---|---|
| `domain` | The site audited |
| `score` | 0–100, weighted by severity |
| `grade` | `A`–`F`, with a plain-words `verdict` |
| `criticalCount` · `importantCount` · `minorCount` | How much is wrong, at a glance |
| `topIssue` · `topIssueFound` | The worst problem, flattened for sorting in a spreadsheet |
| **`emailSummary`** | **Paste-ready paragraph for a client email** |
| `findings` | Every finding: `id`, `label`, `severity`, `found`, `why`, `fix` |
| `passed` | What the site got right — useful for not insulting a prospect |
| `platform` | WordPress, Shopify, Wix, Squarespace… |
| `auditStatus` | `audited` · `unknown` · `invalid_url` — see below |

### Sample output

One row per site. Real output, from `example.com` (findings trimmed to two for length):

```json
{
  "domain": "example.com",
  "url": "https://example.com/",
  "auditStatus": "audited",
  "score": 47,
  "grade": "D",
  "verdict": "Costing the business enquiries",
  "criticalCount": 2,
  "importantCount": 2,
  "minorCount": 3,
  "topIssue": "Page title",
  "topIssueFound": "Title is only \"Example Domain\"",
  "reachable": true,
  "responseMs": 229,
  "pageBytes": 559,
  "auditedAt": "2026-09-03T09:36:18.180Z",
  "emailSummary": "Title is only \"Example Domain\". Too short to say what the business does, so search results carry no message. Fix: Aim for 50-60 characters: name, service, location.\n\nNo phone number, email or contact form on the page. A visitor ready to buy has no way to say so. This is the single most expensive omission a small business site can have. Fix: Put a phone number in the header. Not on a separate contact page - in the header.",
  "findings": [
    {
      "id": "title",
      "label": "Page title",
      "severity": "critical",
      "found": "Title is only \"Example Domain\"",
      "why": "Too short to say what the business does, so search results carry no message.",
      "fix": "Aim for 50-60 characters: name, service, location."
    },
    {
      "id": "contact",
      "label": "Visible contact details",
      "severity": "critical",
      "found": "No phone number, email or contact form on the page",
      "why": "A visitor ready to buy has no way to say so. This is the single most expensive omission a small business site can have.",
      "fix": "Put a phone number in the header. Not on a separate contact page - in the header."
    }
  ],
  "passed": ["Secure connection (HTTPS)", "Mobile ready", "Main heading", "Image alt text"]
}
```

`passed` is there so you do not open a conversation by listing only faults —
telling a prospect what they got right is what makes the rest land.

### How to use it

1. Paste your client list into **Websites**. Domains or full URLs both work.
2. Run it. Nothing to configure — the default input runs as-is.
3. Export to CSV and sort by `score`, or open the **Ready to send** view and
   work down the `emailSummary` column.

Chains onto any Google Maps or lead-finder actor: pass its output straight in
as `startUrls`.

### Pricing

**$0.003 per site audited**, plus a near-zero $0.00005 to start a run.

| You audit | You pay |
|---|--:|
| 100 sites | $0.30 |
| 500 sites | $1.50 |
| 5,000 sites | $15.00 |

A site that could not be checked (`unknown`) still costs a result — the request
was made. That is why the concurrency default is low: needlessly provoking bot
protection would cost you rows that tell you nothing.

Priced above the $0.001 site-checking utilities on purpose. A row here is a
paragraph you send to a client, not a data point you still have to write up.

### Limitations

- **It reads HTML and response headers, not a rendered page.** A site that
  paints its entire content with JavaScript will look emptier than it is. That
  is the trade for being fast and cheap enough to run across 500 sites.
- **It audits one page** — the address you give it, following redirects. It is
  not a crawler.
- **Response time is one sample**, taken from Apify's infrastructure, not a
  benchmark from your customer's location.
- **Sites that refuse us are not graded.** See above. Expect a few `unknown`
  rows on any large list; that is the actor being honest, not failing.

### What it checks

HTTPS · page title · meta description · mobile viewport · `h1` · image alt text ·
Open Graph tags · page weight · response time · **visible contact details** · analytics ·
canonical URL · structured data · favicon.

No browser, no API key, no proxy, no anti-bot. Everything measured here is in the HTML and the
response headers, which is why it is fast and cheap to run across a list of 500.

### The thing that makes it trustworthy

**A site that refuses us is reported as `unknown`, never as a bad grade.**

This is not a detail. Measured on a real run of 1,564 audits in the source tool: **299 of the 389
F grades — 77% of them — were bot protection returning 403**, recorded as *"their website address
does not work."* One of them was a complete, working law firm site that loads fine in any browser.

Telling a business its working website is broken is a worse email than any other you could send,
because it is specific and they can disprove it in one click. So `401`, `403`, `429` and `5xx`
come back as `auditStatus: "unknown"` with the reason in `auditNote` — never as a score. A real
`404` **is** graded `F`, because an address that genuinely does not work is a fact about the
business, and the most sellable finding here.

Unreachable sites are still returned as rows. If you paid for 500 audits you can see which ones
did not happen.

### Input

```json
{
  "domains": ["example.com", "bobs-plumbing.co.uk"],
  "maxItems": 100,
  "minSeverity": "all",
  "concurrency": 4
}
```

`startUrls` is accepted as an alternative, in Apify's URL-list format, so this chains directly
onto a Google Maps scraper or a lead finder with no transform step in between.

`concurrency` is capped at 10 and defaults to 4 on purpose — hitting sites hard is what makes
bot protection return the 403s described above.

### Development

The audit engine is **vendored, not forked**. `src/lib/factory/` is copied verbatim from the
agency tool that is its source of truth, with only import paths rewritten:

```bash
npm run vendor           # re-sync from the factory
npm test                 # fails if the vendored copy has drifted
```

A hand-maintained fork would stop receiving improvements on day one and then slowly start
disagreeing with the source about what a website's problems are — which is worse than not
sharing the code, because two products would tell the same business two different things.
`src/lib/factory/vendor.test.mjs` is what prevents that.

The engine itself has **zero npm dependencies** — it is Node standard library only. `apify` is
the actor's single dependency.

# Actor input Schema

## `domains` (type: `array`):

Domains or full URLs to audit. Paste a client list, or the website column from a Google Maps export.

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

Same thing in Apify's URL-list format, so this actor chains directly onto another actor's output.

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

Hard limit, strictly enforced — you will never be charged for more than this.

## `minSeverity` (type: `string`):

Trim the findings list. The score and counts always reflect every check.

## `concurrency` (type: `integer`):

Kept low on purpose. Hitting sites hard makes bot protection return 403, which looks like a broken website but is not one.

## Actor input object example

```json
{
  "domains": [
    "example.com"
  ],
  "startUrls": [],
  "maxItems": 100,
  "minSeverity": "all",
  "concurrency": 4
}
```

# Actor output Schema

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

Website audit findings in plain English.

# 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 = {
    "domains": [
        "example.com"
    ],
    "maxItems": 100,
    "concurrency": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("alaudinburki/small-business-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 = {
    "domains": ["example.com"],
    "maxItems": 100,
    "concurrency": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("alaudinburki/small-business-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 '{
  "domains": [
    "example.com"
  ],
  "maxItems": 100,
  "concurrency": 4
}' |
apify call alaudinburki/small-business-website-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alaudinburki/small-business-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/jFIRGt3sCS74OIElv/builds/5c7HydWFUfWQgD12w/openapi.json
