# Chain vs Independent Labeler for Google Maps Leads (`ultimatemc/chain-vs-independent-labeler`) Actor

Remove chains and franchises from your local-business leads. Labels each place from a Google Maps dataset or your own list as chain, franchise or independent, with confidence and reasons, using 19,119 known brands, repeated names, shared websites and store-locator URLs. No scraping.

- **URL**: https://apify.com/ultimatemc/chain-vs-independent-labeler.md
- **Developed by:** [Ultimate Foresight Inc](https://apify.com/ultimatemc) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

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?

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

**Chain vs Independent Labeler** cleans local-business lead lists. It labels every place from your **Google Maps scraper dataset** or your own list as **chain**, **franchise**, **independent** or **uncertain**, with a confidence score and the reasons, so you can **remove chains and franchises before outreach** and focus on independent businesses. It makes no requests to Google or any website. Click **Start** with the prefilled example to see results in seconds.

### What does Chain vs Independent Labeler do?

- ✅ Matches business names against **19,119 brands** in the OpenStreetMap **name-suggestion-index**, checking the brand's country and category, after stripping store numbers and suffixes like "- Downtown" or "#1234"
- ✅ Spots **multi-location businesses in your own data**: the same name at 3 or more addresses (adjustable), or one website domain shared by 2 or more addresses
- ✅ Detects **store-locator URLs** such as `/locations/`, `/store/123` or `stores.brand.com`
- ✅ Flags **franchise** brands where Wikidata lists franchising as the business model
- ✅ Returns a **label, confidence (0-1) and plain-English reasons** for every place, and keeps all your original fields
- ✅ Works directly on the output of Google Maps scrapers (fields `title`, `address`, `website`, `countryCode`, `categoryName` are detected automatically)

### Who is it for?

- 📈 **Marketing agencies and freelancers** selling to independent restaurants, salons, dentists, contractors and other local businesses
- 🧾 **SaaS and B2B sales teams** whose buyers are owner-operated businesses, not corporate chains
- 🗺️ **Lead-list builders** who scrape Google Maps and need to drop national brands
- 📊 **Market researchers** measuring how chain-heavy a city or niche is

### How to filter chains out of Google Maps leads

1. Run any Google Maps scraper on Apify and note its dataset ID, or prepare your own list of places.
2. Open this Actor and pick that dataset in **Places dataset ID**, or paste places into **Places** (prefilled with two examples).
3. Click **Start**.
4. In the results, filter `label` = `independent` (dataset input: `chainLabel.label`) and export to CSV, Excel or JSON, or pull the results through the API.

Tip: run your whole list in one go. Repeated names and shared domains are only detected within a single run.

### Input example

This is the prefilled input:

```json
{
  "places": [
    {
      "name": "Starbucks",
      "address": "1 Main St, Austin, TX",
      "website": "https://www.starbucks.com/store-locator/store/1234",
      "countryCode": "US",
      "category": "Coffee shop"
    },
    {
      "name": "Joe's Barber Shop",
      "address": "12 Pine St, Austin, TX",
      "website": "https://joesbarberaustin.com",
      "countryCode": "US",
      "category": "Barber shop"
    }
  ]
}
```

Or use a dataset: `{ "datasetId": "YOUR_GOOGLE_MAPS_DATASET_ID", "repeatThreshold": 3 }`

### Output example

Real output from the prefilled input (run on 2026-10-21):

```json
[
  {
    "name": "Starbucks",
    "address": "1 Main St, Austin, TX",
    "website": "https://www.starbucks.com/store-locator/store/1234",
    "countryCode": "US",
    "category": "Coffee shop",
    "label": "chain",
    "confidence": 0.98,
    "reasons": [
      "Name matches known brand 'Starbucks' in OpenStreetMap name-suggestion-index (exact match, category agrees)",
      "Website URL looks like a store-locator / location page"
    ],
    "brand": "Starbucks",
    "brandWikidata": "Q37158",
    "nsiCategory": "amenity/cafe",
    "sameNameCountInInput": 1,
    "sameDomainCountInInput": 1,
    "websiteDomain": "starbucks.com"
  },
  {
    "name": "Joe's Barber Shop",
    "address": "12 Pine St, Austin, TX",
    "website": "https://joesbarberaustin.com",
    "countryCode": "US",
    "category": "Barber shop",
    "label": "independent",
    "confidence": 0.75,
    "reasons": [
      "No brand match and no multi-location signal found; has its own website domain (joesbarberaustin.com)"
    ],
    "brand": null,
    "brandWikidata": null,
    "nsiCategory": null,
    "sameNameCountInInput": 1,
    "sameDomainCountInInput": 1,
    "websiteDomain": "joesbarberaustin.com"
  }
]
```

With dataset input, your original item is kept and the result is added under `chainLabel`. This real example comes from a test with a Google Maps-style dataset where the same restaurant appeared at two addresses with one website:

```json
{
  "title": "Veracruz All Natural",
  "address": "1704 E Cesar Chavez St, Austin, TX",
  "website": "https://www.veracruztacos.com/",
  "countryCode": "US",
  "categoryName": "Mexican restaurant",
  "placeId": "p2",
  "chainLabel": {
    "label": "chain",
    "confidence": 0.75,
    "reasons": [
      "Website domain veracruztacos.com shared by 2 different addresses in this input"
    ],
    "brand": null,
    "brandWikidata": null,
    "nsiCategory": null,
    "sameNameCountInInput": 2,
    "sameDomainCountInInput": 2,
    "websiteDomain": "veracruztacos.com"
  }
}
```

### How much does it cost to label leads?

This Actor uses **pay-per-event** pricing:

- **$0.0015 per place labeled** (one result row per place)
- a tiny Actor start fee of **$0.00005** per run
- plus the small Apify **platform usage** of the run, paid by you. For the prefilled 2-place example this was about **$0.0001** (256 MB, about 2 seconds).

So **1,000 places cost about $1.50** and **10,000 places about $15**, plus platform usage. You pay only for places labeled, and Apify lets you set a maximum cost per run.

### Use it through the API, integrations or AI agents

- **API:** `POST https://api.apify.com/v2/acts/ultimatemc~chain-vs-independent-labeler/run-sync-get-dataset-items` with your Apify token and the input JSON. JavaScript and Python examples are on the **API** tab.
- **AI agents (MCP):** add `https://mcp.apify.com?tools=ultimatemc/chain-vs-independent-labeler` to Claude, Cursor, ChatGPT or any MCP client so your agent can classify businesses as a tool. See [Apify MCP server](https://docs.apify.com/mcp).
- **Pipelines:** trigger it after your Google Maps scraper finishes with an Apify integration or webhook, and send the clean list to Google Sheets, your CRM or Make, Zapier or n8n.

### How the label is decided

| Signal | Effect |
|---|---|
| Exact or prefix match to a known brand | chain (or franchise), confidence 0.75-0.95, higher when the category agrees |
| Brand's Wikidata entry lists franchising | franchise |
| Same name at 3+ different addresses in your input | chain |
| Same website domain at 2+ different addresses (social and booking sites ignored) | chain |
| Store-locator URL or store number in the name | chain signal (0.6 on its own) |
| Weak partial brand match (e.g. "Express Cleaners" vs "Express"), or a brand known only in another country | uncertain (0.5), check by hand |
| None of the above | independent (0.75 with its own website domain, otherwise 0.65) |

### Data sources and licenses

- **name-suggestion-index** v8.0.20260918, [github.com/osmlab/name-suggestion-index](https://github.com/osmlab/name-suggestion-index), BSD-3-Clause (license bundled with the Actor).
- **Wikidata** franchising flags (queried 2026-10-20), CC0.
- Your own input data. No scraping and no requests to Google or other websites.

### FAQ

#### Does "independent" guarantee the business is independently owned?

No. It means no chain signal was found. Small regional chains that aren't in the brand index and appear only once in your data will look independent.

#### Why are most franchises labeled "chain"?

Public data rarely says whether a location is franchised or corporate-owned. Only 53 brands in the index carry a franchising flag, so most franchised brands are labeled **chain**. For lead filtering, both usually mean "not an owner-operated independent".

#### Which countries work?

Brand matching uses the country code when present and covers brands from many countries in the name-suggestion-index. Repeat-name and shared-domain signals work in any country.

#### Does it scrape Google Maps?

No. Bring the data from any Google Maps scraper or your own CRM export; this Actor only labels it.

### Limitations

- Generic names can collide with brands (e.g. "Shell", "Target"). Category and country checks reduce this but don't remove it.
- Repeat and shared-domain signals only see places in the same run.
- Results depend on the brand index; very new or local brands may be missing.

### Other tools by Ultimate Foresight

- [Ad Copy Policy Checker for Meta, Google & TikTok](https://apify.com/ultimatemc/ad-copy-policy-checker): flags ad wording that may break platform ad policies before you submit.
- [H-1B Visa Sponsor Checker & Score](https://apify.com/ultimatemc/h1b-visa-sponsor-score): adds Department of Labor H-1B sponsorship history and a 0-100 score to company names or job listings.

Mislabeled business? Open an issue on the **Issues** tab with the name, address and website.

# Actor input Schema

## `places` (type: `array`):

Places to label, as a JSON array of objects with name (or title), address, website, countryCode (e.g. US) and category. Extra fields are kept. Prefilled with two examples; replace with your own, or use a dataset below.

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

ID of an Apify dataset with places, e.g. the output of a Google Maps scraper (fields title, address, website, countryCode, categoryName are detected automatically). Each item is copied to the output with an added 'chainLabel' object.

## `repeatThreshold` (type: `integer`):

How many different addresses with the same business name in your input count as a multi-location (chain) signal.

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

Maximum places to process (all are held in memory to compare names and domains).

## Actor input object example

```json
{
  "places": [
    {
      "name": "Starbucks",
      "address": "1 Main St, Austin, TX",
      "website": "https://www.starbucks.com/store-locator/store/1234",
      "countryCode": "US",
      "category": "Coffee shop"
    },
    {
      "name": "Joe's Barber Shop",
      "address": "12 Pine St, Austin, TX",
      "website": "https://joesbarberaustin.com",
      "countryCode": "US",
      "category": "Barber shop"
    }
  ],
  "repeatThreshold": 3,
  "maxItems": 5000
}
```

# Actor output Schema

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

No description

# 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 = {
    "places": [
        {
            "name": "Starbucks",
            "address": "1 Main St, Austin, TX",
            "website": "https://www.starbucks.com/store-locator/store/1234",
            "countryCode": "US",
            "category": "Coffee shop"
        },
        {
            "name": "Joe's Barber Shop",
            "address": "12 Pine St, Austin, TX",
            "website": "https://joesbarberaustin.com",
            "countryCode": "US",
            "category": "Barber shop"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ultimatemc/chain-vs-independent-labeler").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 = { "places": [
        {
            "name": "Starbucks",
            "address": "1 Main St, Austin, TX",
            "website": "https://www.starbucks.com/store-locator/store/1234",
            "countryCode": "US",
            "category": "Coffee shop",
        },
        {
            "name": "Joe's Barber Shop",
            "address": "12 Pine St, Austin, TX",
            "website": "https://joesbarberaustin.com",
            "countryCode": "US",
            "category": "Barber shop",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ultimatemc/chain-vs-independent-labeler").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 '{
  "places": [
    {
      "name": "Starbucks",
      "address": "1 Main St, Austin, TX",
      "website": "https://www.starbucks.com/store-locator/store/1234",
      "countryCode": "US",
      "category": "Coffee shop"
    },
    {
      "name": "Joe'\''s Barber Shop",
      "address": "12 Pine St, Austin, TX",
      "website": "https://joesbarberaustin.com",
      "countryCode": "US",
      "category": "Barber shop"
    }
  ]
}' |
apify call ultimatemc/chain-vs-independent-labeler --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ultimatemc/chain-vs-independent-labeler"
        }
    }
}
```

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/T5ukQh20baXlm0FpC/builds/Z68KJm8INb1JCNMdb/openapi.json
