# Google Maps Businesses Without a Website (Leads) (`steadyscrape/google-maps-no-website-leads`) Actor

Lead finder for local businesses on Google Maps without a website, or with only a Facebook, Instagram or directory page: name, phone, address, rating, reviews, claimed status, hours, map link, within your radius. Lead lists for web designers and agencies, as CSV or API. Pay per lead. No login.

- **URL**: https://apify.com/steadyscrape/google-maps-no-website-leads.md
- **Developed by:** [Steady Scrape](https://apify.com/steadyscrape) (community)
- **Categories:** Lead generation, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 lead (business without a website)s

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

## Google Maps Businesses Without a Website — lead finder for web designers

Find **local businesses on Google Maps that have no website** — or only a Facebook page, an Instagram profile, a Yelp
listing or a booking-app page — in any city, area or postcode, within a radius you choose. Every lead comes with its
**phone number, address, category, rating, review count, opening hours, whether the Google profile is claimed**, and a
link to it on Google Maps.

Businesses that already have a website are checked and skipped: **you only pay for leads**.

### Who it is for

- **Web designers and agencies**: call or visit businesses that need a site, with their phone and reviews in hand.
- **Local SEO and Google Business Profile consultants**: `onlyUnclaimed` finds profiles nobody manages.
- **Social media agencies**: businesses whose only web presence is a Facebook or Instagram page.
- **Lead resellers and SMMA teams**: many trades × many cities in one run, scheduled weekly with only the new leads.

### How it works

1. You give **search terms** (`plumber`, `roofer`, `hair salon`), **locations** (`Austin, TX`, `Manchester`,
   `78701`, or `latitude, longitude`) and a **radius**.
2. Each location is looked up on Google Maps; its centre is the point the search starts from.
3. The Actor reads Google Maps' results for each term around the centre, page after page, then around more points
   spaced across the circle — closer together where businesses are dense, so a big city is covered as well as a small
   one. Places outside the radius are dropped.
4. Each place inside the radius is checked: **no website** (or the kind of non-website you allow) and your filters →
   it is delivered as a lead. A business found by two searches is delivered and charged once.
5. One free summary row per search term and location tells you how many places were checked, how many had a
   website, how many were filtered out, and how many leads came out.

Measured on 2026-10-17: **handyman in Lubbock, TX (10 km): 65 of 116 businesses had no real website**; auto repair in
central Houston: 506 of 899.

### Input example

```json
{
    "searchTerms": ["plumber", "roofer"],
    "locations": ["Austin, TX", "San Antonio, TX"],
    "radius": 15,
    "distanceUnit": "km",
    "maxLeadsPerSearch": 200,
    "requirePhone": true,
    "minReviews": 3
}
```

### Output example

```json
{
    "rowType": "lead",
    "name": "Handyman Depot",
    "category": "Handyman/Handywoman/Handyperson",
    "categories": ["Handyman/Handywoman/Handyperson"],
    "address": "2323 66th St, Lubbock, TX 79412",
    "street": "2323 66th St",
    "city": "Lubbock",
    "postalCode": "79412",
    "state": "Texas",
    "countryCode": "US",
    "phone": null,
    "phoneInternational": null,
    "rating": 4.3,
    "reviewCount": 12,
    "websiteStatus": "social",
    "listedLink": "https://m.facebook.com/66streethio/",
    "claimed": false,
    "closed": false,
    "openingHours": ["Monday: Closed", "Tuesday: 9 AM–5 PM", "Wednesday: 9 AM–5 PM", "Thursday: 9 AM–5 PM", "Friday: 9 AM–5 PM", "Saturday: 9 AM–5 PM", "Sunday: Closed"],
    "placeId": "ChIJNfuDl59t_oYR5fY_LvYn2UI",
    "cid": "4816925214740838117",
    "mapsUrl": "https://www.google.com/maps?cid=4816925214740838117",
    "lat": 33.5339082,
    "lng": -101.8675719,
    "distance": 5.74,
    "distanceUnit": "km",
    "searchTerm": "handyman",
    "location": "Lubbock, TX",
    "scrapedAt": "2026-10-17T12:00:00.000Z"
}
```

#### Lead fields

| field | meaning |
|---|---|
| `name`, `category`, `categories` | the business and its Google categories (the first is the main one) |
| `address`, `street`, `city`, `postalCode`, `state`, `countryCode` | as Google Maps shows them; `null` for a **service-area business** (many trades hide their address) |
| `phone`, `phoneInternational` | as displayed, and in international format (`+1806…`); `null` when the profile shows none |
| `rating`, `reviewCount` | Google stars and number of reviews; `null` when nobody has reviewed it |
| `websiteStatus` | `none` (empty website field), `social` (Facebook, Instagram, TikTok, X, YouTube, Linktree…), `directory` (Yelp, Nextdoor, BBB, a booking or delivery app, the Google profile itself) or `free-builder` (sites.google.com, wixsite.com, square.site…) |
| `listedLink` | what the website field holds instead of a website, or `null` |
| `claimed` | `false` when Google offers "Claim this business" on the profile: nobody manages it |
| `closed` | `true` when Google Maps marks the place closed (only delivered when you include closed businesses) |
| `openingHours` | one line per day, Monday first; empty when the profile lists none |
| `placeId`, `cid`, `mapsUrl` | Google's ids and a link to the profile |
| `lat`, `lng`, `distance` | where Google pins it, and how far from the location's centre in your unit |
| `searchTerm`, `location`, `scrapedAt` | which search found it, and when (UTC) |

#### Search summary rows (free)

`rowType: "search"`, one per search term and location: `status` (`ok`, `blocked`, `location_not_found`,
`not_searched`), `resolvedLocation` (what Google Maps understood), `placesInRadius`, `withWebsite`, `filteredOut`,
`foundByEarlierSearch` (already delivered by another search of the run), `deliveredInEarlierRuns`, `leads`,
`maxLeadsReached`, `searchPoints`, `resultPages`, and a `reason` when something stopped it. The dataset has two views,
**Leads** and **Searches**.

### Filters (never charged)

- **What counts as no website**: an empty website field always; a social profile and a directory/booking page by
  default (each can be turned off); a free builder subdomain if you turn it on.
- **Phone number required**, **only unclaimed profiles**, **minimum rating**, **minimum reviews**, **categories to
  keep** (keyword match on Google's categories — useful because a search for "handyman" also returns painters and
  hardware stores), **include closed businesses** (off by default).
- **Only new leads**: on scheduled runs, skip every business an earlier run already delivered to you. The list is kept
  in a key-value store of your own account (`steadyscrape-google-maps-no-website-leads-state`).

### Pricing

Pay per event, **no start fee**:

| event | price |
|---|---|
| `lead` — one business without a website delivered | **US$0.003** (US$3 per 1,000 leads) |

Businesses with a website, filtered-out businesses, duplicates, places outside the radius, search summaries,
unknown locations and blocked searches are **free**. Your run's spending limit is respected: the run stops at the
last lead it pays for and says so in the summary row.

### Stops when Google asks for verification

The Actor reads the same answers Google Maps gives its own web page, at a moderate pace (two searches at once, under
two requests a second). **If Google ever asks for verification** — its "unusual traffic" page, a CAPTCHA, a JavaScript
check, or any answer that is not Google Maps' normal data — **the run stops sending requests**, every search left is a
free `blocked` row that says so, and the run still finishes cleanly with the leads found so far. It never solves,
bypasses or replays a challenge. Plain overloads (HTTP 429 or 5xx without a challenge) are retried through Apify's
datacenter proxy.

### Limits

- Up to 20 search terms and 20 locations per run; radius 0.5–50 km (or miles); up to 5,000 leads per search.
- Google Maps lists at most about 120–240 places per search point; the Actor adds points across the circle to see
  more, but a very dense category in a huge radius may still hold businesses it does not reach.
- A business with no pin on the map (a few service-area businesses) cannot be placed inside the radius and is left
  out; the summary row counts them as `placesWithoutPosition`.
- Ambiguous location names resolve as Google Maps resolves them (`Springfield` is a landmark in Massachusetts): check
  `resolvedLocation`, add the state, or give `latitude, longitude`. The `country` setting biases short names.
- Results are what a signed-out visitor sees; emails are not on Google Maps and are not included.

### Is it legal?

It reads publicly visible Google Maps listings — business names, phones and addresses the businesses publish for
customers — without logging in. No personal data beyond what a business shows on its public profile. Use the leads in
line with Google's terms and the laws that apply to your outreach (for example CAN-SPAM, TCPA, GDPR).

# Actor input Schema

## `searchTerms` (type: `array`):

The kind of business, as you would type it into Google Maps, one per line: <code>plumber</code>, <code>roofer</code>, <code>hair salon</code>, <code>auto repair</code>. Each term is searched in every location. Up to 20.

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

Where to look, one per line: a city, area, postcode or address as you would type it into Google Maps (<code>Austin, TX</code>, <code>Shoreditch, London</code>, <code>78701</code>), or <code>latitude, longitude</code>. Up to 20. A business found by two searches is delivered and charged once.

## `radius` (type: `number`):

How far from each location's centre to look (0.5 to 50, in the unit below). Businesses outside it are left out, never charged. 10 km covers most of a mid-size city.

## `distanceUnit` (type: `string`):

No description

## `maxLeadsPerSearch` (type: `integer`):

Stop each search term in each location after this many leads (1 to 5,000). The run's spending limit is respected too.

## `includeSocialProfiles` (type: `boolean`):

On: a business whose website field is a Facebook, Instagram, TikTok, X, YouTube or Linktree page is delivered as a lead (<code>websiteStatus: social</code>, with the link).

## `includeDirectoryListings` (type: `boolean`):

On: a website field pointing to Yelp, Nextdoor, Thumbtack, BBB, a booking app (Booksy, Vagaro, Square Appointments…), a delivery app or the Google profile itself is a lead (<code>websiteStatus: directory</code>).

## `includeFreeBuilderSites` (type: `boolean`):

On: a free site such as <code>sites.google.com</code>, <code>*.wixsite.com</code>, <code>*.square.site</code> or <code>\*.godaddysites.com</code> is a lead too (<code>websiteStatus: free-builder</code>).

## `requirePhone` (type: `boolean`):

Keep only businesses that show a phone number. Others are not delivered and not charged.

## `onlyUnclaimed` (type: `boolean`):

Keep only businesses whose Google profile still offers "Claim this business": nobody manages it.

## `minRating` (type: `number`):

Keep only businesses rated at least this many stars (0 to 5). Empty: any rating, including none.

## `minReviews` (type: `integer`):

Keep only businesses with at least this many reviews. Empty: any.

## `categoryFilter` (type: `array`):

Keep only businesses with a Google category containing one of these words (any case), e.g. <code>plumber</code>. Empty: every category the search returns.

## `includeClosed` (type: `boolean`):

Off: places Google Maps marks as closed are left out.

## `onlyNewLeads` (type: `boolean`):

Skip businesses an earlier run of this Actor already delivered to you (remembered in a storage of your own account). Skipped businesses are not charged. Turn it on for scheduled runs.

## `language` (type: `string`):

Language of categories and opening hours, as a code: <code>en</code>, <code>es</code>, <code>de</code>, <code>pt-BR</code>.

## `country` (type: `string`):

Two-letter country code Google answers for (<code>us</code>, <code>gb</code>, <code>au</code>); it also decides which city a short location name means.

## Actor input object example

```json
{
  "searchTerms": [
    "handyman"
  ],
  "locations": [
    "Lubbock, TX"
  ],
  "radius": 10,
  "distanceUnit": "km",
  "maxLeadsPerSearch": 20,
  "includeSocialProfiles": true,
  "includeDirectoryListings": true,
  "includeFreeBuilderSites": false,
  "requirePhone": false,
  "onlyUnclaimed": false,
  "includeClosed": false,
  "onlyNewLeads": false,
  "language": "en",
  "country": "us"
}
```

# Actor output Schema

## `rows` (type: `string`):

No description

## `summary` (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 = {
    "searchTerms": [
        "handyman"
    ],
    "locations": [
        "Lubbock, TX"
    ],
    "radius": 10,
    "maxLeadsPerSearch": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyscrape/google-maps-no-website-leads").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 = {
    "searchTerms": ["handyman"],
    "locations": ["Lubbock, TX"],
    "radius": 10,
    "maxLeadsPerSearch": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyscrape/google-maps-no-website-leads").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 '{
  "searchTerms": [
    "handyman"
  ],
  "locations": [
    "Lubbock, TX"
  ],
  "radius": 10,
  "maxLeadsPerSearch": 20
}' |
apify call steadyscrape/google-maps-no-website-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyscrape/google-maps-no-website-leads"
        }
    }
}
```

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/HbCzFreqKAk8TyYmY/builds/0SxtedaxXhnJBXJsb/openapi.json
