# Maps Location Website Health Audit (`practical_ai_lab_jp/pal-maps-location-website-health-audit`) Actor

Offline audit of branch-directory website links: missing, malformed, off-brand and duplicated URLs.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-maps-location-website-health-audit.md
- **Developed by:** [Practical AI Lab](https://apify.com/practical_ai_lab_jp) (community)
- **Categories:** Lead generation, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 location 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

## Maps Location Website Health Audit

Checks the website column of a branch or store directory that you already hold, for example an export of your own Google Maps business listings. It finds missing and malformed website links, domains that do not match your brand, and several locations that point to the same page. It works entirely offline on the data you provide.

### What it does

- **Missing and malformed links**: empty values, placeholders such as `N/A`, missing `https` prefix, typos like `https//`, whitespace, invalid host names, private or local addresses, non-web schemes.
- **Brand-domain consistency**: compares each domain with the brand domains you list. If you list none and supply at least 3 sites, the dominant domain is inferred from your input and outliers are flagged.
- **Duplicate location URLs**: normalizes links (case, `www`, trailing slash, fragments, tracking parameters, `index.html`) and groups locations sharing one URL.
- **Directory/social links**: flags links to social, directory or maps pages used instead of an own website.

Each row gets a status (`ok`, `warning`, `error`) and issue codes. A `SUMMARY` record in the key-value store lists counts, duplicate groups and notes.

### Privacy

Only `locationId`, `name`, `city` and `websiteUrl` are read. Phone, email and any other columns are ignored. Contact-like values placed in the website field (emails, `tel:` links) are flagged and shown as `[redacted]`. Credentials inside URLs are removed from output. Nothing is collected from any website or service.

### Sample input

```json
{
  "locations": [
    {"locationId": "S001", "name": "Acme Coffee Downtown", "city": "Springfield", "websiteUrl": "https://www.acmecoffee.example/locations/downtown"},
    {"locationId": "S002", "name": "Acme Coffee Riverside", "city": "Springfield", "websiteUrl": "acmecoffee.example/locations/riverside?utm_source=maps"},
    {"locationId": "S003", "name": "Acme Coffee Airport", "city": "Shelbyville", "websiteUrl": "http://acme-coffee-airport.example"},
    {"locationId": "S004", "name": "Acme Coffee Mall", "city": "Shelbyville", "websiteUrl": ""},
    {"locationId": "S005", "name": "Acme Coffee Harbor", "city": "Capital City", "websiteUrl": "https://acmecoffee.example/locations/downtown/"},
    {"locationId": "S006", "name": "Acme Coffee Station", "city": "Capital City", "websiteUrl": "https//acmecoffee.example/station"}
  ],
  "brandDomains": ["acmecoffee.example"],
  "allowSubdomains": true,
  "ignoreTrackingParameters": true,
  "maxLocations": 1000
}
```

You can also paste CSV text with a header row (`locationId,name,city,websiteUrl`) into `csvText`.

### Output fields

`locationId`, `name`, `city`, `inputWebsite`, `domain`, `normalizedUrl`, `status`, `issueCodes`, `issues` (code, severity, message), `duplicateGroupSize`, `duplicateLocationIds`.

### Limitations

- **No live checks.** Links are never opened, so dead pages, redirects, parked domains and wrong-business pages are not detected. A link marked `ok` is only well-formed and consistent.
- **Domain grouping is approximate.** A small built-in list of multi-part suffixes (such as `co.uk`) is used, not the full public suffix list; unusual suffixes may be grouped wrongly.
- **Inferred brand domain is weak evidence**, especially under 30 locations. Franchises and multi-brand groups legitimately use several domains; provide `brandDomains` for reliable results.
- **Shared pages are not always errors.** Some brands intentionally use one store-locator page; review duplicate groups before changing data.
- Input is capped at 5000 rows per run (default 1000). Results depend entirely on the quality of the data you supply.
- This Actor does not scrape Google Maps or any other site and does not verify that a business exists.

### Pricing

The code emits one `location-audited` event per audited row after its result is stored; invalid input fails before any event. No payable plan is configured in this project.

### Local tests

`python -m unittest discover -s tests` (offline; the tests do not need the Apify SDK).

# Actor input Schema

## `locations` (type: `array`):

Branch rows you already hold. Only locationId, name, city and websiteUrl are read; other fields such as phone or email are ignored.

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

Optional CSV with a header row. Recognised columns: locationId, name, city, websiteUrl. Rows are added to the locations list.

## `brandDomains` (type: `array`):

Domains the brand owns, e.g. acmecoffee.example. Leave empty to infer the dominant domain from the input (needs at least 3 sites).

## `allowSubdomains` (type: `boolean`):

Treat shop.acmecoffee.example as matching acmecoffee.example.

## `ignoreTrackingParameters` (type: `boolean`):

Remove utm\_\* and click-id parameters before detecting duplicate URLs.

## `maxLocations` (type: `integer`):

Upper bound of rows audited per run (1 to 5000). Extra rows are skipped and reported.

## Actor input object example

```json
{
  "locations": [
    {
      "locationId": "S001",
      "name": "Acme Coffee Downtown",
      "city": "Springfield",
      "websiteUrl": "https://www.acmecoffee.example/locations/downtown"
    },
    {
      "locationId": "S002",
      "name": "Acme Coffee Riverside",
      "city": "Springfield",
      "websiteUrl": "acmecoffee.example/locations/riverside?utm_source=maps"
    },
    {
      "locationId": "S003",
      "name": "Acme Coffee Airport",
      "city": "Shelbyville",
      "websiteUrl": "http://acme-coffee-airport.example"
    },
    {
      "locationId": "S004",
      "name": "Acme Coffee Mall",
      "city": "Shelbyville",
      "websiteUrl": ""
    },
    {
      "locationId": "S005",
      "name": "Acme Coffee Harbor",
      "city": "Capital City",
      "websiteUrl": "https://acmecoffee.example/locations/downtown/"
    },
    {
      "locationId": "S006",
      "name": "Acme Coffee Station",
      "city": "Capital City",
      "websiteUrl": "https//acmecoffee.example/station"
    }
  ],
  "brandDomains": [
    "acmecoffee.example"
  ],
  "allowSubdomains": true,
  "ignoreTrackingParameters": true,
  "maxLocations": 1000
}
```

# Actor output Schema

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

Download rows from the latest run in JSON/CSV.

# 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 = {
    "locations": [
        {
            "locationId": "S001",
            "name": "Acme Coffee Downtown",
            "city": "Springfield",
            "websiteUrl": "https://www.acmecoffee.example/locations/downtown"
        },
        {
            "locationId": "S002",
            "name": "Acme Coffee Riverside",
            "city": "Springfield",
            "websiteUrl": "acmecoffee.example/locations/riverside?utm_source=maps"
        },
        {
            "locationId": "S003",
            "name": "Acme Coffee Airport",
            "city": "Shelbyville",
            "websiteUrl": "http://acme-coffee-airport.example"
        },
        {
            "locationId": "S004",
            "name": "Acme Coffee Mall",
            "city": "Shelbyville",
            "websiteUrl": ""
        },
        {
            "locationId": "S005",
            "name": "Acme Coffee Harbor",
            "city": "Capital City",
            "websiteUrl": "https://acmecoffee.example/locations/downtown/"
        },
        {
            "locationId": "S006",
            "name": "Acme Coffee Station",
            "city": "Capital City",
            "websiteUrl": "https//acmecoffee.example/station"
        }
    ],
    "brandDomains": [
        "acmecoffee.example"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-maps-location-website-health-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 = {
    "locations": [
        {
            "locationId": "S001",
            "name": "Acme Coffee Downtown",
            "city": "Springfield",
            "websiteUrl": "https://www.acmecoffee.example/locations/downtown",
        },
        {
            "locationId": "S002",
            "name": "Acme Coffee Riverside",
            "city": "Springfield",
            "websiteUrl": "acmecoffee.example/locations/riverside?utm_source=maps",
        },
        {
            "locationId": "S003",
            "name": "Acme Coffee Airport",
            "city": "Shelbyville",
            "websiteUrl": "http://acme-coffee-airport.example",
        },
        {
            "locationId": "S004",
            "name": "Acme Coffee Mall",
            "city": "Shelbyville",
            "websiteUrl": "",
        },
        {
            "locationId": "S005",
            "name": "Acme Coffee Harbor",
            "city": "Capital City",
            "websiteUrl": "https://acmecoffee.example/locations/downtown/",
        },
        {
            "locationId": "S006",
            "name": "Acme Coffee Station",
            "city": "Capital City",
            "websiteUrl": "https//acmecoffee.example/station",
        },
    ],
    "brandDomains": ["acmecoffee.example"],
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-maps-location-website-health-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 '{
  "locations": [
    {
      "locationId": "S001",
      "name": "Acme Coffee Downtown",
      "city": "Springfield",
      "websiteUrl": "https://www.acmecoffee.example/locations/downtown"
    },
    {
      "locationId": "S002",
      "name": "Acme Coffee Riverside",
      "city": "Springfield",
      "websiteUrl": "acmecoffee.example/locations/riverside?utm_source=maps"
    },
    {
      "locationId": "S003",
      "name": "Acme Coffee Airport",
      "city": "Shelbyville",
      "websiteUrl": "http://acme-coffee-airport.example"
    },
    {
      "locationId": "S004",
      "name": "Acme Coffee Mall",
      "city": "Shelbyville",
      "websiteUrl": ""
    },
    {
      "locationId": "S005",
      "name": "Acme Coffee Harbor",
      "city": "Capital City",
      "websiteUrl": "https://acmecoffee.example/locations/downtown/"
    },
    {
      "locationId": "S006",
      "name": "Acme Coffee Station",
      "city": "Capital City",
      "websiteUrl": "https//acmecoffee.example/station"
    }
  ],
  "brandDomains": [
    "acmecoffee.example"
  ]
}' |
apify call practical_ai_lab_jp/pal-maps-location-website-health-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,practical_ai_lab_jp/pal-maps-location-website-health-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/hfWZ1c9eSqrZd6wfK/builds/UqZvwUvQ9NLnq7ied/openapi.json
