# Website Redesign Lead Scorer: staleness, consent, accessibility (`fractionalhqforyou/website-redesign-lead-scorer`) Actor

Scores business websites 0-100 for redesign opportunity, for agency prospecting. Measures how old the site looks, what tracking fires before any cookie choice, axe-core accessibility failures, and mobile and https basics. Returns the top three reasons as pasteable outreach lines.

- **URL**: https://apify.com/fractionalhqforyou/website-redesign-lead-scorer.md
- **Developed by:** [Jessy Mariau](https://apify.com/fractionalhqforyou) (community)
- **Categories:** Lead generation, SEO tools, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 site scoreds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Redesign Lead Scorer

If you sell web design or web builds, finding businesses is the easy part. The part that eats the day is working out which 40 of the 300 on your list are worth an email, and what to put in the first line so it does not read like every other agency pitch.

I built this Actor because I kept doing that by hand. You open a prospect's site. The footer says 2018. The cookie banner turns out to be decorative, because Google Analytics fired before you touched it. You run axe and find sixty accessibility failures. Four minutes gone, on one company, and there are 299 left.

Feed the list in here instead. You get one row per site: a 0 to 100 redesign score, a HOT, WARM or COLD band, every reason with its numbers, and the three plainest reasons already written as sentences you can paste into the email.

### What it does

Each website is loaded in a real Chromium, the way a visitor would load it. Four things get measured.

**How old the site looks.** The newest `lastmod` in the sitemap. The newest date in the page's structured data and in its `<time>` elements. The footer copyright year, the `Last-Modified` header, the `<meta name="generator">` tag, the jQuery version. Dates the site publishes about its own content carry the weight here. A footer year is usually printed by a script and rolls over on its own every January, so it only gets used when there is nothing else to go on.

**What loads before a visitor chooses anything.** Every network request is recorded from the moment the page starts loading, and for three seconds after. Nothing is clicked. No banner is dismissed. Whatever fired in that window fired before a visitor had a say. The Actor then names the well-known tracking services it recognises. It counts the cookies already set, split first-party and third-party. It looks for a privacy policy link, and for a consent tool from a list of the usual vendors.

**Accessibility.** axe-core is injected into the rendered page and run against the WCAG 2.1 A and AA rules. You get the number of failing elements, the split by impact, and the five rules that failed on the most elements.

**The basics.** Mobile viewport tag. https, and whether the plain http address redirects to it. Plain-http files on a secure page. Page weight and request count. Contact form present or absent, plus the social networks linked.

Those four feed a documented rubric that produces the score. No personal data is collected anywhere: phone is a yes or no, social links are hostnames only, and no email address is ever read or stored.

### The pitch angles

This is the field I actually built the Actor for. A score tells you who to email. It does not tell you what to say, and a generic opener gets deleted.

So the top three reasons come back rewritten as one plain sentence each, with the real numbers already in them. I keep them flat and unexcited on purpose, because the specific detail is what earns the reply and adjectives only get in its way. Some real ones from live runs:

- The footer still says 2019 and the newest page in the sitemap is from 2021.
- The newest page in the sitemap is from 2020, 6 years ago.
- Google Analytics, the Meta pixel and Hotjar load before a visitor makes any cookie choice.
- The homepage has 34 serious or critical accessibility problems, all of them from one rule: elements must meet minimum color contrast ratio thresholds.
- The pages carry no mobile viewport tag, so a phone shows the desktop layout shrunk to fit.
- The site still loads jQuery 1.11.3, and the current major version is 3.

Every number in one of those sentences is read back out of the row it describes. Nothing is generated, so nothing can be invented.

### What you need

Nothing. No API key, no account anywhere, and no proxy for the great majority of business websites.

Run it with no websites at all and you get two sample rows in demo mode, charged nothing, so the shape is visible before you point it at a real list.

### How to run it

A prospecting list, homepage only, all four checks:

```json
{
  "websites": "smithandsonsplumbing.co.uk\nthe-old-mill-cafe.com\nhttps://example-garage.co.uk",
  "maxPages": 1
}
```

Deeper on a shortlist, following up to five internal pages per site:

```json
{
  "websites": "smithandsonsplumbing.co.uk\nthe-old-mill-cafe.com",
  "maxPages": 5,
  "checks": ["freshness", "accessibility", "consent", "tech"]
}
```

A fast first pass with no browser. You keep the copyright year, generator, viewport and https. You lose the tracker list and the axe-core numbers:

```json
{
  "websites": "a-very-long-list-pasted-here",
  "useBrowser": false
}
```

The `OUTPUT` record in the key-value store holds the summary: sites processed, the HOT, WARM and COLD counts, events charged, and the top 10 by score with their first pitch angle.

### Output fields

One row per website, grouped the same way the checks are.

The result itself, and what happened during the visit.

| Field | What it holds |
|---|---|
| `input` | your line, exactly as pasted |
| `final_url`, `status` | the final URL after redirects, and its HTTP status |
| `browser_used`, `browser_error` | false on a browser failure, plus the reason |
| `pages_scanned`, `page_title` | pages behind this row, and the homepage title |
| `redesign_score`, `score_band` | 0 to 100, and HOT, WARM or COLD |
| `reasons` | every rubric line triggered, with its numbers and points |
| `pitch_angles` | the top three of those, one pasteable sentence each |
| `checks_run` | the checks switched on, for a fair comparison between rows |
| `error` | the reason for no score, or null |
| `checked_at` | when |

These are the freshness signals.

| Field | What it holds |
|---|---|
| `newest_signal_year`, `newest_signal_source`, `stale_years` | the newest dated evidence, its origin, and its age in years |
| `sitemap_newest_lastmod`, `copyright_year` | the two strongest individual signals |
| `jsonld_newest_year`, `time_tag_newest_year`, `content_newest_year` | dates from the page's own markup |
| `last_modified` | the header, reported but never scored |

These cover consent and tracking.

| Field | What it holds |
|---|---|
| `consent_view_available` | false without a browser, marking the rest of this group unknown rather than clean |
| `cmp_detected` | the consent tool found, or null |
| `trackers_before_consent`, `trackers_before_consent_count` | named tracking services from before any consent choice |
| `cookies_before_consent`, `third_party_cookies_before_consent`, `first_party_cookies_before_consent` | cookies already set at that point |
| `has_privacy_link`, `has_cookie_policy_link` | booleans |

These come from axe-core and the cheap static counts.

| Field | What it holds |
|---|---|
| `a11y_ran` | false on an axe failure, with every number below null rather than zero |
| `a11y_violations_total`, `a11y_critical`, `a11y_serious`, `a11y_moderate`, `a11y_minor`, `a11y_serious_plus_critical` | failing elements, not failing rules |
| `a11y_rules_failed`, `a11y_top_rules` | the rule count, and the five worst with their element counts |
| `images_total`, `images_without_alt`, `missing_html_lang`, `inputs_without_label`, `empty_links_or_buttons`, `heading_level_skips` | cheap static counts, present even without axe |

These are the technical basics.

| Field | What it holds |
|---|---|
| `page_weight_kb`, `request_count` | HTML, CSS and JS only, with images, media and fonts blocked |
| `has_viewport_meta`, `uses_https`, `redirects_to_https`, `mixed_content_requests` | the basics |
| `generator`, `jquery_version` | the page's own claim about what built it |
| `has_contact_form`, `phone_found`, `social_links` | a boolean, a boolean, and hostnames |

### The score

Six lines, adding up to 100.

| Signal | Max | Points |
|---|---|---|
| Staleness | 35 | years since the newest dated evidence: 6 or more → 35, 4 to 5 → 26, 3 → 18, 2 → 10, newer → 0 |
| Accessibility | 25 | 4 per critical failing element, 1.5 per serious one, capped at 25 |
| Consent and tracking | 15 | 4 per tracking service loaded before a choice, up to 12; plus 3 for no consent tool; plus 3 for no privacy policy link; capped at 15 |
| Mobile viewport | 8 | 8 for no viewport meta tag |
| https | 7 | 7 for no https at all; otherwise 3 for a plain http address that never redirects, or for plain-http files on the page |
| Old platform | 10 | 6 for jQuery 1.x, 4 for jQuery 2.x, 4 for a generator tag naming old WordPress, Joomla 1 to 3, Drupal 7, FrontPage, Dreamweaver or MSHTML |

`HOT` is 70 and above. `WARM` is 40 to 69. `COLD` is under 40.

A line only fires when it has something to say, and an unmeasurable signal never scores at all. A site whose accessibility could not be tested does not collect 0 accessibility points as though it had passed. It gets no accessibility line, and `a11y_ran` says why. Turning a check off in the input removes its lines too, so only compare scores across runs with the same `checks`.

### Pricing

Pay per event: one `site-scored` charge per site that produced a score. A site that could not be fetched, that answered 403 or 404, or that you typed wrong costs nothing at all. The demo run costs nothing. Following internal pages with `maxPages` adds no charge, and neither does switching all four checks on. The price is per site, whatever you turn on.

### Limits

- The homepage only, unless you raise `maxPages`. That is deliberate. For prospecting, the homepage is what the buyer will look at and what you will talk about.
- The consent findings describe what the page loaded on its own, before anyone chose anything, and they stop there. Whether any of it breaks a law is a question for the prospect and their lawyer. It also makes a far better opening line than an accusation.
- The tracking services named come from a list of well-known ones. A site can run something the list does not cover, so read the count as a floor.
- Some large publishers serve their consent tool from their own domain, so ad blockers cannot see it. That hides the vendor name from this Actor too. Where the shape is there but the vendor is not identifiable, `cmp_detected` says `a first-party consent tool` rather than guessing at one.
- The accessibility numbers are axe-core on the pages scanned, against WCAG 2.1 A and AA. Automated testing catches a portion of the real barriers. Treat the number as a reason to open a conversation, and never sell it as a WCAG audit.
- Blocking images shifts the accessibility count very slightly, because an image that never loads never reaches the DOM as one. I measured this on a live site: 7 critical failures with images on, 6 with them off, a single `image-alt` node of difference. Everything else matched to the node.
- A footer copyright year is usually generated, so it loses to dates the site publishes about its content. `Last-Modified` is a server file timestamp rather than evidence that anyone touched the design, so it appears in the row and stays out of the score.
- JS-heavy sites and sites behind a bot check can beat the browser. Those fall back to a plain HTTP read, `browser_used` comes back false, and the tracker and axe fields come back null rather than zero. Any site answering 400 or above gets no score at all, because treating a bot-block page as a homepage is how a lead list fills up with confident nonsense.
- Small business sites on shared hosting sometimes refuse a datacentre IP outright. One site in my own testing returned 200 from my laptop and 401 from an Apify server on the same afternoon. That is the case `proxyConfiguration` exists for, and the row says `401` rather than inventing a score.
- Page weight covers HTML, CSS and JS. Images, media and fonts are blocked to keep runs cheap, so the number sits below what a visitor really downloads.
- Only public http and https hostnames are accepted. IP addresses, local hostnames and everything else are refused before any request goes out.

### Pair it with a workflow

If you run n8n, my free templates on https://n8n.io/creators/fractional-hq take a list like this Actor's output and turn it into a cold email draft per prospect, plus the follow-ups. Filter to `score_band` of `HOT`, drop `pitch_angles[0]` into the opening sentence, and the specific detail that makes the email land is already written for you.

The scoring model and its blind spots are written up at https://fractionalhq.uk/use-cases/redesign-lead-scoring

### Built by Fractional HQ

I run Fractional HQ, a one-man studio for design and automation. I do not write code by hand: I architect, review and hold the standard while AI agents do the typing. That method runs my own estate: seven brands, a Postgres operations system, more than 100 scheduled jobs, published n8n templates, Zapier Silver certified.

Want this wired into your CRM and outreach, or built and handed over working? Get in touch: https://fractionalhq.uk/brief

# Actor input Schema

## `websites` (type: `string`):

One website per line. A bare domain is fine (example.co.uk) and https is assumed. Leave empty to get a demo dataset that charges nothing.

## `maxPages` (type: `integer`):

1 is the homepage only, which is what most prospecting runs want. Up to 5 follows internal links and adds their accessibility failures and dates to the same row.

## `checks` (type: `array`):

Leave all four on unless you are trying to save time. Each check you turn off removes its points from the score, so scores are only comparable between runs with the same checks.

## `useBrowser` (type: `boolean`):

On by default. A real browser is what makes the consent and accessibility checks possible at all. Switch it off for a fast, cheap pass that reads the HTML only: you still get the copyright year, generator, viewport and https, but no tracker list and no axe-core numbers.

## `maxSites` (type: `integer`):

A safety cap on how many lines are processed, whatever you paste in. Hard limit 500.

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

Off by default and most business websites need none. Switch it on only if a site keeps timing out or blocking. Proxy traffic is billed to your Apify account on top of this Actor's events.

## Actor input object example

```json
{
  "websites": "example.com\nexample.org",
  "maxPages": 1,
  "checks": [
    "freshness",
    "consent",
    "accessibility",
    "tech"
  ],
  "useBrowser": true,
  "maxSites": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `sites` (type: `string`):

One row per website: the redesign score and band, every signal behind it, the triggered rubric reasons and the pitch angles written as sentences.

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

The OUTPUT record: sites processed, how many landed HOT, WARM and COLD, how many events were charged, and the top 10 by score with their first pitch angle.

# 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": `example.com
example.org`
};

// Run the Actor and wait for it to finish
const run = await client.actor("fractionalhqforyou/website-redesign-lead-scorer").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": """example.com
example.org""" }

# Run the Actor and wait for it to finish
run = client.actor("fractionalhqforyou/website-redesign-lead-scorer").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": "example.com\\nexample.org"
}' |
apify call fractionalhqforyou/website-redesign-lead-scorer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fractionalhqforyou/website-redesign-lead-scorer"
        }
    }
}

```

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/JPed4ZPZDkWDFKzwp/builds/Q6OHKOh6E82jNIekx/openapi.json
