# Website Opportunity Scorer - Local Business Leads (`prevailing_glow/website-opportunity-scorer`) Actor

Score local business websites from a Google Maps dataset, URL list or CSV: HTTPS, mobile, old tech, SEO, schema, CTAs. 0-100 score, top reasons, pitch angle.

- **URL**: https://apify.com/prevailing_glow/website-opportunity-scorer.md
- **Developed by:** [Dave West](https://apify.com/prevailing_glow) (community)
- **Categories:** Lead generation, SEO tools, Marketing
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 website scoreds

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

## Website Opportunity Scorer - Local Business Leads

Find the local businesses whose **website needs work**, and know **what to pitch** before you call.

Feed this Actor the results of a **Google Maps scraper run** (or a plain list of websites, business objects or CSV). For every business it checks the website with fast plain HTTP requests and returns a **0-100 opportunity score** (higher = the site needs more work = better lead), a **grade (A-F)**, a **hot / warm / cold** priority, the **top 3 reasons in plain English** ("Not mobile-friendly", "No HTTPS", "Copyright notice says 2017") and a **suggested pitch angle** with a one-line pitch that uses the business's own name, category, city and reviews.

Built for **web design agencies, SEO agencies and freelancers** who sell websites, redesigns, local SEO and maintenance to small businesses: kappers, loodgieters, restaurants, dentists, garages, shops.

- **No website or only a Facebook/Instagram/Treatwell page?** Scored 90-100 as a "new website" lead (cheaper event).
- **Site down, 404, parked domain or "coming soon"?** Scored 85-95 as a "website rescue" lead.
- **Outdated, not mobile-friendly, no HTTPS?** Redesign lead with the reasons to mention.
- **Modern site with missing SEO basics, no click-to-call, no analytics?** Local SEO / conversion / tracking pitch.
- **Healthy site?** Grade A, "maintenance / SEO retainer" angle, so you don't waste a cold call on a redesign pitch.

Also included: an **HTML report** sorted by score and a **SUMMARY** with the most common problems, CMS share and score distribution of the whole list.

### How it works

1. Run any Google Maps scraper (for example `compass/crawler-google-places`) for "kapper Utrecht", "loodgieter Amersfoort", ...
2. Start this Actor and pick that run's **dataset** in the dataset picker (or paste the dataset ID).
3. Get one row per business, sorted by opportunity in the HTML report. Export to CSV/Excel or your CRM.

This Actor **never scrapes Google Maps** itself; it only reads the dataset you give it (read-only) and visits each business's own website homepage.

### What is checked

All checks use the homepage HTML from plain HTTP requests (https and http), a TLS handshake and a technology fingerprint database. No browser, no proxy, no JavaScript rendering.

| Area | Checks |
|---|---|
| Security | HTTPS, certificate valid / expired / wrong host / incomplete chain, expiring within 14 days, http → https redirect, mixed content |
| Mobile | Responsive viewport meta tag |
| Outdated tech | End-of-life CMS (Joomla < 4, Drupal < 9, TYPO3 < 11, Magento 1, PrestaShop < 1.7), WordPress < 6.0, jQuery < 3.5, Flash, table layouts and `<font>`/`<center>` tags, copyright year 3+ / 5+ years old, DIY site builders (Wix, Jimdo, ...) |
| SEO basics | Title (missing / length), meta description, H1, noindex, **LocalBusiness structured data** (schema.org and subtypes such as HairSalon, Plumber, Restaurant), Open Graph tags |
| Conversion | Contact / booking button or form, click-to-call (`tel:`) link, WhatsApp link, booking tool (Salonized, Treatwell, Calendly, ...) |
| Tracking | Google Analytics, Tag Manager, Matomo, Plausible, Clarity, Hotjar, Facebook Pixel, ... |
| Speed | Homepage response time and HTML weight; optional **Core Web Vitals** from real Chrome users (CrUX) |
| Broken / not a site | DNS failure, timeout, connection refused, HTTP 4xx/5xx, parked or for-sale domain, placeholder ("binnenkort online", "under construction", server default page, empty page), redirects to a social or booking platform |
| Not scored | Sites behind bot protection (Cloudflare challenge, SiteGround captcha, 403) or whose hosting firewall drops our HTTPS connection while http:// answers are output as `blocked` with no score and are **not charged** |

### Scoring rules

Every point comes from a documented rule (no AI). The score is the sum of all matching rules, capped at 100.

**Whole-site outcomes** (the score is set to this value):

| Rule | Points |
|---|---|
| `no_website` - no website on the listing | 100 |
| `dns_fail` - domain does not resolve | 95 |
| `parked` - parking / for-sale page | 92 |
| `platform_only` - listing links to Facebook, Instagram, Treatwell, Linktree, ... | 90 |
| `unreachable` - timeout, connection refused, TLS failure | 90 |
| `http_error` - homepage returns 4xx/5xx | 85 |
| `placeholder` - coming soon / under construction / default server page / empty | 85 |

**Website rules** (added up):

| Rule | Points |
|---|---|
| `no_https` - no HTTPS | 25 |
| `cert_invalid` - certificate expired / wrong host / self-signed | 25 |
| `no_viewport` - not mobile-friendly | 22 |
| `noindex` - homepage hidden from Google | 15 |
| `flash` - uses Flash | 15 |
| `outdated_cms` - end-of-life CMS version | 10 |
| `table_layout` - table layout / deprecated HTML tags | 10 |
| `copyright_old` - copyright 5+ years old (3-4 years: 6) | 10 |
| `old_wordpress` - WordPress older than 6.0 | 8 |
| `no_title` - no page title | 8 |
| `slow` - server response (time to first byte incl. redirects) > 3.5 s (2-3.5 s: 4) | 8 |
| `cwv_fail` - fails Core Web Vitals (add-on) | 8 |
| `cert_chain` - incomplete certificate chain | 6 |
| `no_https_redirect` - http:// does not redirect to https:// | 6 |
| `no_meta_description` - no meta description | 6 |
| `no_cta` - no contact / booking button, form or booking tool | 6 |
| `no_h1` - no H1 heading | 5 |
| `no_local_schema` - no LocalBusiness structured data | 5 |
| `no_phone_link` - phone number not click-to-call | 5 |
| `old_jquery` - jQuery older than 3.5 | 5 |
| `cert_expiring` - certificate expires within 14 days | 5 |
| `no_analytics` - no analytics or tag manager | 4 |
| `diy_builder` - DIY site builder | 4 |
| `mixed_content` - http:// resources on an https page | 4 |
| `heavy_html` - homepage HTML > 1 MB | 4 |
| `cwv_poor_lcp` - LCP > 4 s for real users (add-on) | 4 |
| `no_open_graph` - no Open Graph tags | 3 |
| `title_length` - title shorter than 30 or longer than 60 characters | 2 |

**Grades**: A < 15, B 15-29, C 30-49, D 50-69, F 70+. **Priority**: hot 50+, warm 25-49, cold < 25.

**Pitch angle**: follows the opportunity type (redesign leads get the strongest of security / mobile / modern redesign, SEO leads local SEO, conversion leads calls & bookings or tracking, healthy sites a maintenance / SEO retainer); otherwise the angle whose rules scored the most points (ties: the more urgent angle). Angles: new website, website rescue / rebuild, security & trust (HTTPS), mobile-friendly redesign, modern redesign, local SEO, more calls & bookings, speed optimisation, analytics & tracking, maintenance / SEO retainer.

### Input

| Field | Description |
|---|---|
| `datasetId` | Dataset of a Google Maps scraper run (dataset picker, ID, or Console/API URL). compass/crawler-google-places and Outscraper-style fields are recognised. |
| `businesses` | Websites as strings, or objects `{ "name", "category", "city", "website", "phone", "rating", "reviewCount", ... }`. Maps scraper items can be pasted as-is. |
| `urls` | Plain list of websites. |
| `csvText` | CSV with a header row (English or Dutch column names: naam, website, plaats, telefoon, ...). |
| `minScore` | Only output and charge businesses with at least this score (default 0). |
| `onlyCategories` | Keep only categories containing these words, e.g. `kapper`. |
| `includeNoWebsite` | Output no-website / social-only businesses (default true). |
| `skipClosed` | Skip permanently closed businesses (default true). |
| `maxBusinesses` | Limit (0 = all). |
| `checkCoreWebVitals` | Core Web Vitals add-on (default off), see below. |
| `googleApiKey` | Optional own Google API key for the add-on (encrypted secret field). |
| `maxConcurrency`, `requestTimeoutSecs`, `userAgent` | Advanced. |

All sources can be combined; duplicates (same place ID, or same name and website) are scored once. Leave the input **empty** for a **free preview** on a few example Dutch businesses.

```json
{
    "datasetId": "<default dataset ID of your Google Maps scraper run>",
    "minScore": 30,
    "onlyCategories": ["kapper", "barbier"],
    "includeNoWebsite": true
}
```

### Output

One row per business. Business fields first (only these 11 are ever copied from the input), then the score, reasons, pitch and every signal (flat, CSV-friendly). Shortened example:

```json
{
    "name": "Kapsalon Voorbeeld",
    "category": "Kapper",
    "address": "Voorstraat 1, 3512 AJ Utrecht",
    "city": "Utrecht",
    "website": "https://www.kapsalon-voorbeeld.nl/",
    "phone": "030 123 4567",
    "rating": 4.7,
    "reviewCount": 87,
    "openingHours": "dinsdag: 09:00 tot 18:00; ...",
    "placeId": "ChIJ...",
    "mapsUrl": "https://www.google.com/maps/search/?api=1&query=...",
    "opportunityScore": 72,
    "grade": "F",
    "leadPriority": "hot",
    "opportunityType": "redesign",
    "topReasons": [
        "Not mobile-friendly: no mobile viewport tag, so phones show a shrunken desktop page",
        "No HTTPS: browsers mark the site as \"Not secure\"",
        "Copyright notice says 2016: the site looks unmaintained for 10 years"
    ],
    "pitchAngle": "mobile",
    "pitchLine": "Mobile-friendly redesign: most visitors of a kapper come from phones, and the current site is not built for them. Kapsalon Voorbeeld has 87 Google reviews (4.7★), so people are looking for it.",
    "https": false,
    "mobileViewport": false,
    "cms": "WordPress",
    "wordpressVersion": "4.9.8",
    "copyrightYear": 2016,
    "localBusinessSchema": false,
    "phoneLink": false,
    "hasAnalytics": false,
    "responseTimeMs": 1840,
    "scoreBreakdown": [{ "id": "no_viewport", "points": 22, "reason": "..." }],
    "chargedEvents": ["website-scored"]
}
```

Dataset views: **Leads** (score, grade, business, top reasons, pitch angle) and **Website checks**. The key-value store has **OUTPUT.html** (report sorted by score) and **SUMMARY** (JSON).

### Privacy (GDPR)

The Actor is built to keep personal data out of your lead lists:

- Only these business fields are read from the dataset and output: **name, category, address, city, website, business phone, rating, review count, opening hours, place ID, Google Maps URL**.
- **Emails, owner or person names, review texts and reviewer names are never read or output**, even if your scraper dataset contains them (the dataset is read with a field whitelist, so review arrays and enrichment data are not even downloaded). Email addresses that appear inside a business name or on the homepage are replaced by `[email removed]`.
- Pasted objects and CSV columns outside the whitelist (e.g. `email`, `owner`) are ignored, with a note in the summary.
- Only the public homepage of each business website is fetched.

You are responsible for how you contact businesses (e.g. B2B outreach rules in your country).

### Core Web Vitals add-on (optional)

Turn on `checkCoreWebVitals` to query Google's **Chrome UX Report** for each site's origin: LCP, INP and CLS of real Chrome users over 28 days. Sites that fail get extra points and a speed reason. Most small local-business sites have too little traffic to be in CrUX; those are **not charged** for the add-on. Optionally `psiFallback` runs a PageSpeed Insights lab test for sites without CrUX data (slow, 10-60 s per site).

The Actor's own key is used by default (shared rate limit). For large runs paste your own Google API key (Chrome UX Report API enabled) in `googleApiKey`; it is stored encrypted and only used for your run.

Data: Chrome UX Report by Google, [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). This Actor is not affiliated with Google.

### Pricing

Pay per event, you only pay for results:

| Event | Price | When |
|---|---|---|
| Website scored | $0.003 ($3 per 1,000) | a website was fetched and scored (including broken, parked and placeholder sites) |
| No-website lead | $0.0005 ($0.50 per 1,000) | a business without an own website (none, or only a social / booking page) |
| Core Web Vitals check | $0.003 | add-on on, and CrUX (or the lab fallback) returned data for the site |

**Free**: blocked sites (bot protection), businesses below your `minScore`, duplicates, closed businesses, and the empty-input preview. Set a **maximum charge per run** in the run options: the Actor checks the budget before every site and stops cleanly.

### Limitations

- Only the **homepage** is checked (no crawl): a contact page or schema on a sub-page is not seen.
- **No JavaScript rendering**: sites built fully client-side may look emptier than they are (they are usually flagged as an empty page / placeholder only when the HTML has no text, no links and no scripts).
- Speed is the server response time (time to first byte, including redirects) measured from the Actor's datacenter (US), not from a phone; it is a rough signal. Use the Core Web Vitals add-on for real-user speed.
- When a hosting firewall drops our HTTPS connection but http:// answers, HTTPS support cannot be verified: the site is scored from the http version without "No HTTPS" points (`httpsInconclusive: true`), or output as `blocked` when nothing loads.
- Some hosting firewalls block datacenter traffic: those sites are output as `blocked`, unscored and free.
- Copyright-year and placeholder detection are pattern-based (Dutch, English and German phrases).
- The score says how much the **website** needs work, not how good the business is.

### FAQ

**Does it work outside the Netherlands?** Yes. The checks are language-independent; Dutch/English/German phrases are recognised for placeholders, parking pages and CTAs.

**Which Google Maps scrapers are supported?** Any dataset with a website field. compass/crawler-google-places (`title`, `categoryName`, `website`, `totalScore`, `reviewsCount`, `url`, `placeId`, `openingHours`) and Outscraper-style (`name`, `site`, `full_address`, `rating`, `reviews`, `place_id`, `location_link`) fields are mapped automatically.

**Can I re-score the same list later?** Yes, run again with the same dataset; each run is independent.

**Can I get emails?** No, on purpose. Use the website and business phone from the output for outreach.

# Actor input Schema

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

The default dataset of a Google Maps scraper run (e.g. <code>compass/crawler-google-places</code>), or a dataset ID / Console URL. Only business name, category, address, city, website, phone, rating, review count, opening hours, place ID and Maps URL are read: emails, reviews and people data are never read or output.

## `businesses` (type: `array`):

Websites as strings, or objects such as <code>{"name": "...", "category": "Kapper", "city": "Utrecht", "website": "https://...", "rating": 4.6, "reviewCount": 38}</code>. Rows without a website are no-website leads. Google Maps scraper items can be pasted as-is.

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

Plain list of websites, one per line (e.g. <code>example.nl</code>). Google Maps URLs are skipped.

## `csvText` (type: `string`):

Paste CSV with a header row (comma, semicolon or tab). Recognised columns (English/Dutch): name/naam, website/url, category/branche, address/adres, city/plaats, phone/telefoon, rating, reviews, opening hours, place id, maps url. Other columns (e.g. email) are ignored. Without a header every cell that looks like a domain is scored.

## `minScore` (type: `integer`):

Only output (and charge for) businesses with at least this score. 0 = everything. Filtered rows are still fetched but not charged.

## `onlyCategories` (type: `array`):

Keep only businesses whose category contains one of these words (case-insensitive), e.g. <code>kapper</code>, <code>loodgieter</code>. Empty = all.

## `includeNoWebsite` (type: `boolean`):

Output businesses that have no website or only a Facebook/Instagram/booking page as no-website leads (cheaper event).

## `skipClosed` (type: `boolean`):

Skip rows marked permanently closed in the Google Maps data.

## `maxBusinesses` (type: `integer`):

Stop after this many businesses (after de-duplication and filters). 0 = no limit.

## `checkCoreWebVitals` (type: `boolean`):

Query the Chrome UX Report for each site's origin (LCP, INP, CLS from real visitors, 28 days). Charged only when CrUX has data for the site (most small local sites have none). Failing sites get extra points.

## `psiFallback` (type: `boolean`):

Slow (10-60 s per site). Runs a Lighthouse mobile lab test for sites without CrUX data and charges the same Core Web Vitals event when it returns a score. Best used with your own API key.

## `googleApiKey` (type: `string`):

Optional. By default the Actor's key is used (shared rate limit). For big runs paste your own key with the <b>Chrome UX Report API</b> (and <b>PageSpeed Insights API</b>) enabled; it is stored encrypted and used only for this run.

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

Websites checked in parallel.

## `requestTimeoutSecs` (type: `integer`):

Timeout per HTTP request. A homepage that does not answer in time counts as not loading.

## `userAgent` (type: `string`):

Override the User-Agent header. Default identifies the Actor.

## Actor input object example

```json
{
  "businesses": [
    {
      "name": "Kapsalon Lilo",
      "category": "Kapper",
      "city": "Utrecht",
      "website": "https://kapsalonlilo.nl/"
    },
    {
      "name": "Hairstudio IM",
      "category": "Kapper",
      "city": "Utrecht",
      "website": "https://hairstudio-im.nl/"
    },
    {
      "name": "Loodgieter Alert",
      "category": "Loodgieter",
      "city": "Utrecht",
      "website": "https://loodgieteralert.nl/"
    },
    {
      "name": "WW Loodgietersbedrijf",
      "category": "Loodgieter",
      "city": "Utrecht",
      "website": "https://wwloodgietersbedrijf.nl/"
    },
    {
      "name": "Tweewielers Ronald Martens",
      "category": "Fietsenwinkel",
      "city": "Utrecht",
      "website": "https://www.tweewielersronaldmartens.nl/"
    },
    {
      "name": "Studio Bibob",
      "category": "Kapper",
      "city": "Tuitjenhorn",
      "website": "https://www.studiobibob.nl/"
    },
    {
      "name": "Demo Kapsalon (fictional row without a website)",
      "category": "Kapper",
      "city": "Utrecht",
      "rating": 4.6,
      "reviewCount": 38
    }
  ],
  "minScore": 0,
  "includeNoWebsite": true,
  "skipClosed": true,
  "maxBusinesses": 0,
  "checkCoreWebVitals": false,
  "psiFallback": false,
  "maxConcurrency": 10,
  "requestTimeoutSecs": 20
}
```

# Actor output Schema

## `report` (type: `string`):

All leads sorted by opportunity score with top reasons and pitch angles, plus the most common problems.

## `leads` (type: `string`):

One row per business: score, grade, top 3 reasons, pitch angle and contact basics.

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

Full rows with every website signal and the score breakdown.

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

Counts, score distribution, most common problems, CMS share and charges.

# 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 = {
    "businesses": [
        {
            "name": "Kapsalon Lilo",
            "category": "Kapper",
            "city": "Utrecht",
            "website": "https://kapsalonlilo.nl/"
        },
        {
            "name": "Hairstudio IM",
            "category": "Kapper",
            "city": "Utrecht",
            "website": "https://hairstudio-im.nl/"
        },
        {
            "name": "Loodgieter Alert",
            "category": "Loodgieter",
            "city": "Utrecht",
            "website": "https://loodgieteralert.nl/"
        },
        {
            "name": "WW Loodgietersbedrijf",
            "category": "Loodgieter",
            "city": "Utrecht",
            "website": "https://wwloodgietersbedrijf.nl/"
        },
        {
            "name": "Tweewielers Ronald Martens",
            "category": "Fietsenwinkel",
            "city": "Utrecht",
            "website": "https://www.tweewielersronaldmartens.nl/"
        },
        {
            "name": "Studio Bibob",
            "category": "Kapper",
            "city": "Tuitjenhorn",
            "website": "https://www.studiobibob.nl/"
        },
        {
            "name": "Demo Kapsalon (fictional row without a website)",
            "category": "Kapper",
            "city": "Utrecht",
            "rating": 4.6,
            "reviewCount": 38
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("prevailing_glow/website-opportunity-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 = { "businesses": [
        {
            "name": "Kapsalon Lilo",
            "category": "Kapper",
            "city": "Utrecht",
            "website": "https://kapsalonlilo.nl/",
        },
        {
            "name": "Hairstudio IM",
            "category": "Kapper",
            "city": "Utrecht",
            "website": "https://hairstudio-im.nl/",
        },
        {
            "name": "Loodgieter Alert",
            "category": "Loodgieter",
            "city": "Utrecht",
            "website": "https://loodgieteralert.nl/",
        },
        {
            "name": "WW Loodgietersbedrijf",
            "category": "Loodgieter",
            "city": "Utrecht",
            "website": "https://wwloodgietersbedrijf.nl/",
        },
        {
            "name": "Tweewielers Ronald Martens",
            "category": "Fietsenwinkel",
            "city": "Utrecht",
            "website": "https://www.tweewielersronaldmartens.nl/",
        },
        {
            "name": "Studio Bibob",
            "category": "Kapper",
            "city": "Tuitjenhorn",
            "website": "https://www.studiobibob.nl/",
        },
        {
            "name": "Demo Kapsalon (fictional row without a website)",
            "category": "Kapper",
            "city": "Utrecht",
            "rating": 4.6,
            "reviewCount": 38,
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("prevailing_glow/website-opportunity-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 '{
  "businesses": [
    {
      "name": "Kapsalon Lilo",
      "category": "Kapper",
      "city": "Utrecht",
      "website": "https://kapsalonlilo.nl/"
    },
    {
      "name": "Hairstudio IM",
      "category": "Kapper",
      "city": "Utrecht",
      "website": "https://hairstudio-im.nl/"
    },
    {
      "name": "Loodgieter Alert",
      "category": "Loodgieter",
      "city": "Utrecht",
      "website": "https://loodgieteralert.nl/"
    },
    {
      "name": "WW Loodgietersbedrijf",
      "category": "Loodgieter",
      "city": "Utrecht",
      "website": "https://wwloodgietersbedrijf.nl/"
    },
    {
      "name": "Tweewielers Ronald Martens",
      "category": "Fietsenwinkel",
      "city": "Utrecht",
      "website": "https://www.tweewielersronaldmartens.nl/"
    },
    {
      "name": "Studio Bibob",
      "category": "Kapper",
      "city": "Tuitjenhorn",
      "website": "https://www.studiobibob.nl/"
    },
    {
      "name": "Demo Kapsalon (fictional row without a website)",
      "category": "Kapper",
      "city": "Utrecht",
      "rating": 4.6,
      "reviewCount": 38
    }
  ]
}' |
apify call prevailing_glow/website-opportunity-scorer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,prevailing_glow/website-opportunity-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/MNGcJ1y9ocYnr0lPu/builds/hJKbecCItXyxLn9T4/openapi.json
