# SiteVerdict — Local Business Leads (Google Places API + OSM) (`siteverdict/siteverdict-local-business-leads`) Actor

Find local businesses by category and place (e.g. plumbers in Bristol) from OpenStreetMap (free, no key) or the official Google Places API with your own key. Phones, websites, ratings, hours. Grid search beyond 60 results, cost cap, no scraping.

- **URL**: https://apify.com/siteverdict/siteverdict-local-business-leads.md
- **Developed by:** [Aman Thakran](https://apify.com/siteverdict) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## SiteVerdict — Local Business Leads (Google Places API + OpenStreetMap)

**Lists of local businesses (name, category, address, phone, website, rating, opening hours) from official sources only: the Google Places API with your own key, and OpenStreetMap open data.**

Type `plumbers in Bristol`, `cafes near PL1 1AA` or `dentists in Leeds`. You get a clean table you can filter, export to CSV/Excel or send to your CRM.

- **OpenStreetMap mode works out of the box.** It needs no key and the data is open (ODbL), so you can store it and reuse it with attribution.
- **Google mode uses the official Places API (New) with your own Google Maps Platform key.** It never scrapes google.com, never runs a headless browser against Google and never rotates proxies.

Built by SiteVerdict (BharatFare Travel and Technologies Ltd, UK).

***

### Why an API-based Actor instead of a Google Maps scraper?

| | Scraping google.com/maps | This Actor |
|---|---|---|
| **How data is fetched** | Headless browser plus proxies, parsing Google's HTML | Google's official Places API (New) with your key, plus OpenStreetMap's public APIs |
| **Reliability** | Breaks when Google changes the page, and blocks and CAPTCHAs slow runs | Documented, versioned JSON APIs with clear error codes |
| **Terms** | Breaches Google's Terms of Service (automated access to the consumer site) | Google: you are a Google Maps Platform customer using the API as documented. OSM: open licence. |
| **Cost control** | Pay per place, but the scrape can stall | Up-front cost estimate, a hard budget cap and Google's free monthly usage |
| **Data you can keep** | Unclear | OSM: keep it with attribution (ODbL). Google: place IDs can be kept forever; most other Google content cannot be stored (see Compliance). |

**The trade-off:** Google's API returns at most 60 places per search area and 5 reviews per place, and it does not return e-mails or "popular times". This Actor covers big areas with a grid search (below). It does not try to get around Google's limits.

### Who it is for

- **Agencies and freelancers** building prospect lists, such as "accountants without a website in Bath".
- **Sales and partnerships teams** that need local SMB lists with phone numbers and websites.
- **Market research:** how many vets, gyms or cafés are in an area, with ratings (Google) or opening hours (OSM).
- **Developers and AI agents** that need a compliant local-business lookup step in a workflow.

***

### Quick start

#### 1. Free: OpenStreetMap (no key)

```json
{ "queries": ["cafes in Plymouth", "hairdressers in Exeter"], "countryCode": "gb" }
```

#### 2. Google Places API with your key, with a budget cap

```json
{
  "queries": ["plumbers in Bristol"],
  "source": "google",
  "googleApiKey": "<your key>",
  "googleFieldPreset": "contact",
  "googleBudgetUsd": 5,
  "hasWebsite": true
}
```

#### 3. Check the Google cost first (dry run, no Google calls)

```json
{ "queries": ["dentists in Manchester"], "source": "google", "googleDryRun": true }
```

The estimate is saved in the key-value store as `GOOGLE_COST_ESTIMATE`. Without a key, Google searches always run as a dry run.

#### 4. Your own area: point and radius, bounding box or polygon

```json
{ "queries": ["restaurants"], "lat": "51.5136", "lng": "-0.1365", "radiusMeters": 1500, "source": "both", "googleApiKey": "<your key>" }
```

***

### What you get (one row per business)

| Field | Google | OSM | Notes |
|---|---|---|---|
| `source`, `sourceId` | yes | yes | `google:<place_id>` or `osm:node/123` |
| `placeId` / `osmType` + `osmId` | yes | yes | Stable IDs used for de-duplication |
| `name`, `primaryCategory`, `categories` | yes | yes | Google types / OSM tags such as `craft=plumber` |
| `address`, `street`, `houseNumber`, `city`, `postcode`, `region`, `country`, `countryCode` | yes | when tagged | OSM addresses depend on local mappers |
| `lat`, `lng` | yes | yes | Google: delete or refresh within 30 days (see `latLngCacheExpiresAt`) |
| `phone` (national), `phoneInternational` | Contact preset | when tagged | OSM phones are normalised with libphonenumber |
| `website`, `domain`, `hasWebsite` | Contact preset | when tagged | `domain` is the registrable domain (e.g. `example.co.uk`) |
| `googleMapsUrl` / `osmUrl` | yes | yes | Link to the source listing |
| `rating`, `reviewCount`, `priceLevel` | Contact preset | — | OSM has no ratings |
| `openingHours`, `openNow` | Contact preset | `openingHours` / `openingHoursRaw` | |
| `businessStatus` | yes | closed if tagged | `OPERATIONAL`, `CLOSED_TEMPORARILY`, `CLOSED_PERMANENTLY` |
| `description` | Atmosphere preset | when tagged | |
| `reviews` (max 5) | opt-in | — | Reviewer names are a separate opt-in (personal data) |
| `email` | — | opt-in | Off by default (UK GDPR/PECR, see below) |
| `attribution`, `license`, `termsNotice` | yes | yes | Keep these with the data |

The dataset has three views: **Businesses**, **Lead list** (CRM-friendly columns) and **Hours, ratings and status**. The key-value store has these records:

- `RUN_SUMMARY`: counts, filter rejections, Google requests per SKU and spend.
- `GOOGLE_COST_ESTIMATE`
- `AGENCY_LEAD_FINDER_INPUT`, when you enable that output.

#### Sample: real OpenStreetMap row (Plymouth city centre, 11 Oct 2026)

```json
{
  "source": "osm", "sourceId": "osm:node/3435845294", "osmType": "node", "osmId": 3435845294,
  "name": "Daisy's Tea Rooms and Coffee Shop", "primaryCategory": "Cafe", "categories": ["amenity=cafe"],
  "address": "6 Harbour Avenue, Plymouth, PL4 0BJ", "postcode": "PL4 0BJ", "countryCode": "GB",
  "lat": 50.3705471, "lng": -4.1318398, "phoneInternational": "+44 1752 257933",
  "osmUrl": "https://www.openstreetmap.org/node/3435845294",
  "attribution": "© OpenStreetMap contributors", "license": "ODbL-1.0 (https://www.openstreetmap.org/copyright)"
}
```

***

### Using your own Google Maps Platform key (about 5 minutes)

1. Go to **console.cloud.google.com** and create a project, for example "lead-lists".
2. Open **Billing** and link a billing account. Google requires one even if you stay within the free monthly usage.
3. Open **APIs & Services → Library**, search for **"Places API (New)"** and click **Enable**. This Actor uses the *New* API (`places.googleapis.com`), not the legacy Places API.
4. Open **APIs & Services → Credentials → Create credentials → API key**. Then click **Edit API key**:
   - **API restrictions → Restrict key → Places API (New)** only.
   - **Application restrictions:** Apify runs come from changing IP addresses, so IP restrictions usually do not work. Keep this key only for this Actor and rotate it if it leaks.
5. **Set a budget alert:** open **Billing → Budgets & alerts → Create budget** (for example £20/month) and set e-mail alerts at 50%, 90% and 100%. Google budgets *alert* but do not stop spending.
6. **Hard limit (recommended):** open **APIs & Services → Places API (New) → Quotas** and lower the per-day request quotas to a level you are comfortable with. Requests above the quota fail with `RESOURCE_EXHAUSTED`. This Actor treats that as a clean stop.
7. Paste the key into **"Google Maps Platform API key"**. It is a secret input: Apify stores it encrypted, and this Actor never logs it or writes it to the output.

**Common errors, in plain English:**

- `PERMISSION_DENIED` / `SERVICE_DISABLED`: Places API (New) is not enabled, or billing is off.
- `API_KEY_INVALID`: the key is wrong or deleted.
- `RESOURCE_EXHAUSTED`: you have hit a quota. The Actor backs off with exponential delays, then stops cleanly.

### What Google charges you (list prices, checked 11 Oct 2026)

Google bills each **request** at the SKU of the most expensive field you ask for. The field preset you choose decides the SKU:

| Preset | Fields | Text Search SKU | USD per 1,000 requests | Free requests / month |
|---|---|---|---|---|
| IDs only | place ID | Text Search Essentials (IDs Only) | $0 | unlimited |
| Basic | name, address, location, types, Maps link, status | Text Search Pro | $32 | 5,000 |
| Contact (default) | + phone, website, rating, reviews count, price level, hours | Text Search Enterprise | $35 | 1,000 |
| Atmosphere, or any preset with reviews | + editorial summary, reviews | Text Search Enterprise + Atmosphere | $40 | 1,000 |

Other requests:

- **Place Details** (the "Refresh stored place IDs" input): Essentials $5, Pro $17, Enterprise $20 and Enterprise + Atmosphere $25 per 1,000 requests.
- **Nearby Search:** Pro $32, Enterprise $35 and Enterprise + Atmosphere $40 per 1,000 requests.

Source: [Google Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing), first volume band (0–100k). Prices change, so the `googlePriceOverrides` input lets you update them without waiting for a new version.

**One Text Search page returns up to 20 places and is one request.** A search area returns at most 3 pages (60 places).

**Worked examples (Contact preset, list price, before free usage):**

- **One town centre (1 tile):** at most 3 requests = **$0.105** for up to 60 places, about $1.75 per 1,000 places.
- **A city split into 20 tiles:** expected about $1.40. Worst case for the first pass is 20 × 3 × $0.035 = **$2.10**. With re-splitting (max 40 tiles) the absolute ceiling is **$4.20**.
- **IDs only:** **$0** to Google. Refresh the details later, only for the IDs you need.
- **Refresh 1,000 stored place IDs with Contact fields:** 1,000 Place Details Enterprise = **$20**. The first 1,000 each month are free.

The **Google budget cap** (default $5) stops the run *before* a request would cross the cap. The run ends cleanly and everything found so far is kept.

### How grid search gets past the 60-result cap

1. The place you type, such as "Bristol", is geocoded to a bounding box. OpenStreetMap Nominatim is the default; you can switch to Google.
2. The box is split into tiles of about `tileSizeKm` (default 2 km). Tiles outside your circle or polygon are dropped.
3. Each tile is searched with a `locationRestriction` rectangle.
4. A tile that returns the full 60 is **split into 4 and searched again** (quadtree), up to `maxSplitDepth` and `maxTilesPerQuery`.
5. Results are **de-duplicated by Google place ID / OSM id** across tiles and searches.

Half of `maxTilesPerQuery` goes to the first grid and the other half to splits. If tiles still hit the cap at the limit, the log tells you, so you can use smaller tiles or allow more.

**Polygons and Google:** Google's terms forbid point-in-polygon analysis of Places coordinates. The polygon is only used to choose which tiles to search, so Google results near the edge are not clipped. OSM results are clipped to your exact polygon.

### Filters

- **Minimum rating / minimum reviews:** Google only. OSM has no ratings, so OSM rows would all be excluded.
- **Only businesses with a website**, optionally **their own website**: Facebook, Instagram, Yell, Linktree and similar don't count.
- **Only businesses WITHOUT their own website:** useful for web-design prospecting.
- **Only with a phone number**, **open now** (Google), **skip closed businesses** (on by default).

Filtered-out businesses are never charged.

### Pricing of this Actor (pay per event)

| Event | When | Price |
|---|---|---|
| `osm-place` | Each OpenStreetMap business saved to the dataset | $0.001 ($1 per 1,000) |
| `google-place` | Each Google Places business saved to the dataset | $0.0015 ($1.50 per 1,000) |

Our price is deliberately low because **with Google you also pay Google directly**. You are never charged for:

- duplicates
- filtered-out rows
- errors
- dry runs
- Google requests themselves (Google bills those to you)

The run respects your **maximum total charge** and stops cleanly when it is reached.

### Agency Lead Finder hand-off

The SiteVerdict Agency Lead Finder audits each business's website for speed, tracking and AI-search issues and scores the lead. There are two ways to hand this Actor's results to it:

- **Easiest:** give the Agency Lead Finder this run's **run ID** or **dataset ID**. Our field names map onto its inputs automatically: `name`, `website`, `phone`, `address`, `city`, `postcode`, `countryCode`, `primaryCategory`, `rating`, `reviewCount`, `googleMapsUrl`, `placeId` and `businessStatus`.
- **Or** turn on **"Export for SiteVerdict Agency Lead Finder"**. The key-value record `AGENCY_LEAD_FINDER_INPUT` then holds:
  - `businesses`: ready to paste into its "businesses" input. There is one entry per own website domain, so chains sharing a site appear once; Facebook and Yell-style pages are kept per business. Businesses without a website are kept, because it reports them as no-website leads.
  - `websites`: the same list as plain URLs.
  - `runId`

***

### Compliance, attribution and caching

*This section explains the rules as we understand them. It is not legal advice.*

#### Google Maps Platform content (Google mode)

You call Google with **your own key**, so **you** are the Google Maps Platform customer, bound by your agreement with Google (non-EEA [Terms](https://cloud.google.com/maps-platform/terms) or, for billing addresses in the EEA, the [EEA Terms](https://cloud.google.com/terms/maps-platform/eea)). Key points, checked 11 Oct 2026:

- **Storage and export are restricted.** Terms 3.2.3(a) says the customer will not "export, extract, or otherwise scrape Google Maps Content for use outside the Services", including to "copy and save business names, addresses, or user reviews".
  - **Place IDs are exempt** and may be stored indefinitely (Service Specific Terms, caching section).
  - **Latitude/longitude** may be cached for up to **30 consecutive days** (Service Specific Terms 14.3). Every Google row has `latLngCacheExpiresAt`.
  - **What this means in practice:** use Google rows inside your own application or workflow and refresh them through the API. Do not build a permanent exported database of Google names, addresses or phone numbers. If you need a list you can keep, use **OpenStreetMap mode** or the **IDs only** preset, and refresh details when you need them ("Refresh stored place IDs").
- **Attribution.** When you display Google content, show the Google Maps logo, or the text "Google Maps" where space is limited. Keep it separate from other sources. Every Google row carries `attribution: "Google Maps"`. If you display reviews, credit each review's author and link to `googleMapsUri`. Reviews of places in France must also show the visit date. ([Places API policies](https://developers.google.com/maps/documentation/places/web-service/policies))
- **No non-Google maps.** Do not plot Places content on a non-Google map (Service Specific Terms 14.2).
- **No point-in-polygon analysis on Places coordinates** (Terms 3.2.3(c)(iv)). That is why Google results are never clipped to your polygon.
- **No mixing:** Google and OSM rows are never merged into one record. Each row keeps its own source and attribution.

#### OpenStreetMap (OSM mode)

- **Licence:** data © OpenStreetMap contributors, under the **Open Database License (ODbL 1.0)**. Credit "© OpenStreetMap contributors" wherever you use or show it ([copyright page](https://www.openstreetmap.org/copyright)). If you publicly share a database derived from it, that database must also be offered under the ODbL.
- **Fair use of public servers:**
  - **Nominatim:** at most 1 request per second, an identifying User-Agent, no bulk geocoding ([policy](https://operations.osmfoundation.org/policies/nominatim/)). This Actor geocodes each place name once per run, at no more than about 1 request per second.
  - **Overpass:** we send one query at a time with pauses, and back off on HTTP 429/504. The public instance asks users to stay well under about 10,000 queries and 1 GB per day.
  - **Heavy users** should set their own **Overpass endpoint** and **Nominatim endpoint**, or pass coordinates or a bounding box so no geocoding is needed.

#### Personal data (UK GDPR / PECR)

- Business listings can include personal data. Examples are sole traders' names, mobile numbers and e-mails.
- **Reviewer names** and **OSM e-mail addresses** are **off by default**.
- When you turn them on, you become the controller of that data. You need a lawful basis, must honour objections, and must follow PECR: unsolicited marketing e-mails and texts to individuals, including sole traders, generally need prior consent.
- Corporate subscribers (limited companies) are treated differently, but they still have the right to object.
- The ICO's direct-marketing guidance is the reference for the UK.

#### Where data goes and how long it is kept

- **Sent to:** OpenStreetMap's public Nominatim (place-name geocoding) and Overpass servers in OSM mode, or the endpoints you set; Google Places API (with your own key) in Google mode. Each receives only the search details needed for your query (the area or place name, and the category or tag filters).
- **Stored:** the default dataset (one row per business) and key-value store of the run (`RUN_SUMMARY`, `GOOGLE_COST_ESTIMATE`, an optional ready-made `AGENCY_LEAD_FINDER_INPUT`, and a resume-state record), inside your own Apify account. There is no named store.
- **Retention is set by Apify, not SiteVerdict:** default storages are kept according to your Apify plan ([Apify data retention](https://docs.apify.com/platform/storage/usage#data-retention)). That can be longer than Google allows you to cache Google content (for example the 30-day limit on coordinates above), so delete or refresh Google-mode runs to stay within your Google terms.
- **What SiteVerdict keeps:** nothing. The Actor runs in your Apify account and sends no data to SiteVerdict; our contact address is included in the Nominatim User-Agent, as OSM's policy requires.

### FAQ

**Is this the same data as Google Maps?** Google mode returns data from Google's official Places API, which is the same place database that powers Google Maps. It does not include fields the API does not offer, such as e-mails, popular times or more than 5 reviews.

**Why did my OSM search return fewer results than Google?** OpenStreetMap is mapped by volunteers. Cafés, pubs, shops and restaurants are well covered in UK towns. Trades (plumbers, electricians), offices and home-based businesses are often missing. Use Google mode, or "Both", for those.

**My category is not recognised in OSM mode.** About 60 common categories map to OSM tags; for example, plumbers maps to `craft=plumber` and estate agents to `office=estate_agent`. Anything else falls back to a business-name match. For exact control, set **Custom OpenStreetMap tags**, such as `shop=bakery`.

**Can I run this without a Google key?** Yes. OpenStreetMap mode needs no key. Google searches without a key produce only a cost estimate.

**Does the budget cap include Google's free monthly usage?** No. The cap uses list prices, so your real bill is the same or lower.

**What happens if the run is migrated or restarted?** Progress is saved: completed tiles, pending split tiles, seen IDs and Google spend so far. A resumed run continues where it stopped, without re-charging duplicates.

**Can I store Google results in my CRM?** See Compliance. Google's terms allow storing place IDs but restrict copying and saving names, addresses and reviews outside Google Maps Platform services. Check your own agreement with Google.

### Limits

- **Google:** at most 60 results per search area (20 for Nearby Search), at most 5 reviews per place, and no e-mails. Coverage of large areas depends on `tileSizeKm` and `maxTilesPerQuery`, and areas crossing the antimeridian are not supported.
- **OSM:** completeness varies by place and category. Phone numbers, websites and addresses appear only when mappers added them. `openNow` is not evaluated for OSM.
- **Public OSM servers are shared.** Runs can be slowed by HTTP 429/504; the Actor waits and retries. For thousands of searches, use your own Overpass/Nominatim endpoints.
- **Cost figures** use Google list prices checked 11 Oct 2026. Always confirm on Google's pricing page.

# Changelog

This Actor's version history is a separate document: https://apify.com/siteverdict/siteverdict-local-business-leads/changelog.md

# Actor input Schema

## `queries` (type: `array`):

One search per line in the form "<what> in <where>", e.g. "plumbers in Bristol", "cafes near PL1 1AA", "dentists in Leeds". The place is geocoded with OpenStreetMap Nominatim (or Google, see Geocoder). Without "in <where>" the search uses the area below (coordinates / bounding box / polygon).

## `categories` (type: `array`):

Alternative to Searches: every category is searched in every location, e.g. \[plumbers, electricians] x \[Bristol, Bath].

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

City, town, postcode or address, one per line. Used with Categories.

## `source` (type: `string`):

OpenStreetMap: free open data (ODbL), no key, coverage varies by area. Google Places API: needs your own Google Maps Platform API key (Google bills you per request). Both: runs both and keeps each record's source.

## `maxResultsPerQuery` (type: `integer`):

Stop each search after this many businesses are saved.

## `maxResultsTotal` (type: `integer`):

Safety cap for the whole run (you also control spend with the run's maximum total charge).

## `countryCode` (type: `string`):

Two-letter country code, e.g. "gb", "us", "in". Limits geocoding to that country, sets Google's region and formats phone numbers. Leave empty for worldwide.

## `lat` (type: `string`):

Search around a point instead of a named place (decimal degrees, e.g. 50.3714).

## `lng` (type: `string`):

Decimal degrees, e.g. -4.1427.

## `radiusMeters` (type: `integer`):

Radius around the centre point. Max 50,000.

## `boundingBox` (type: `string`):

"south,west,north,east" in decimal degrees, e.g. "50.360,-4.160,50.380,-4.120".

## `polygon` (type: `object`):

A GeoJSON Polygon or Feature (coordinates in \[lng, lat] order). For Google, the polygon only decides which grid tiles are searched: Google's terms forbid point-in-polygon analysis of Places coordinates, so Google results near the edge are not clipped.

## `minRadiusMeters` (type: `integer`):

When a location is tiny (a postcode or address), search this radius around it.

## `tileSizeKm` (type: `number`):

Large areas are split into tiles of about this size; tiles that hit Google's 60-results-per-search cap are split again (quadtree). Smaller tiles = more complete results but more Google requests.

## `maxTilesPerQuery` (type: `integer`):

Upper bound on areas searched per search including re-splits (half for the initial grid, half for splitting). Main lever on Google cost: each tile costs 1-3 Google requests.

## `maxSplitDepth` (type: `integer`):

How many times a saturated tile may be split into 4.

## `osmTileSizeKm` (type: `number`):

OSM areas larger than 150 km² are queried in tiles of this size to keep each Overpass request light.

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

Your own key with "Places API (New)" enabled (see README for a 5-minute setup with budget alerts). Stored encrypted by Apify, never logged or written to the output. Without a key, Google searches run as a cost estimate only.

## `googleFieldPreset` (type: `string`):

IDs only: place IDs (free SKU, storable). Basic: name, address, location, types, Maps link, status (Pro SKU). Contact: + phone, website, rating, review count, price level, hours (Enterprise SKU). Atmosphere: + editorial summary (Enterprise + Atmosphere SKU).

## `googleBudgetUsd` (type: `number`):

The run stops cleanly before Google requests would exceed this amount at Google's list prices (before your free monthly usage). A cost estimate is saved as GOOGLE_COST_ESTIMATE.

## `googleDryRun` (type: `boolean`):

Plan the grid and estimate Google cost without calling Google.

## `googleSearchMode` (type: `string`):

Text Search (default): keyword search, up to 60 per tile. Nearby Search: by Google place type only, up to 20 per tile, needs "Google place type".

## `googleIncludedType` (type: `string`):

A Google Table A type such as "plumber", "cafe", "dentist". Biases Text Search (or restricts it with strict filtering); required for Nearby Search.

## `googleStrictTypeFiltering` (type: `boolean`):

With a Google place type: return only places of exactly that type.

## `googleIncludeServiceAreaBusinesses` (type: `boolean`):

Include businesses with no public address (e.g. mobile tradespeople). Text Search only.

## `googlePlaceIds` (type: `array`):

Place IDs you stored earlier (Google allows storing place IDs). Each costs one Place Details request at the tier of the chosen fields.

## `googleExtraFields` (type: `array`):

Additional Place fields, e.g. "shortFormattedAddress", "priceRange". The most expensive field decides the SKU for every request.

## `includeReviews` (type: `boolean`):

Adds the up-to-5 reviews Google returns. Switches requests to the Enterprise + Atmosphere SKU ($40 per 1,000). Off by default.

## `includeReviewerNames` (type: `boolean`):

Reviewer names and profile links are personal data under UK GDPR. Off by default. If you display reviews, Google requires you to credit the author.

## `geocoder` (type: `string`):

How place names become search areas. Nominatim (OpenStreetMap, free, 1 request/second). Google uses one Text Search Pro request per place name (billed to your key).

## `googleMaxConcurrency` (type: `integer`):

Requests in flight at once. Quota errors are retried with exponential backoff.

## `languageCode` (type: `string`):

Language for Google results, e.g. "en", "en-GB", "fr".

## `googlePriceOverrides` (type: `object`):

USD per 1,000 requests if Google's prices change or you have a volume discount, e.g. {"textSearch.enterprise": 30}. Keys: textSearch|nearbySearch|details . idsOnly|essentials|pro|enterprise|atmosphere.

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

Google only (OpenStreetMap has no ratings, so OSM results would all be excluded). Also sent to Google as a server-side filter.

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

Google only (needs the Contact preset or higher).

## `hasWebsite` (type: `boolean`):

Keep only businesses that list a website.

## `ownWebsiteOnly` (type: `boolean`):

With the option above: ignore Facebook, Instagram, Yell, Linktree etc. as websites.

## `noWebsiteOnly` (type: `boolean`):

Web-design prospecting: keep businesses with no website or only a social/listing page.

## `hasPhone` (type: `boolean`):

Keep only businesses that list a phone number.

## `openNow` (type: `boolean`):

Google: only places open at request time. Not applied to OpenStreetMap.

## `operationalOnly` (type: `boolean`):

Drop places marked temporarily/permanently closed (Google businessStatus; OSM disused/closed tags).

## `osmTags` (type: `array`):

Override the category mapping with OSM tags, e.g. "craft=plumber", "shop=bakery", "office" (any value). See wiki.openstreetmap.org/wiki/Map_features.

## `includeEmails` (type: `boolean`):

Some OSM entries list a contact e-mail. Off by default: for sole traders an e-mail is personal data, and UK PECR restricts unsolicited marketing e-mails to individuals.

## `overpassUrl` (type: `string`):

Default: the public overpass-api.de instance (fair use: well under 10,000 requests/day). For heavy use, run your own Overpass server or use a commercial instance.

## `nominatimUrl` (type: `string`):

Default: the public nominatim.openstreetmap.org (max 1 request/second, no bulk geocoding). Use your own instance for large jobs.

## `osmContact` (type: `string`):

E-mail or URL added to the User-Agent sent to Overpass/Nominatim, as their usage policies ask.

## `agencyLeadFinderExport` (type: `boolean`):

Also save the businesses as key-value record AGENCY_LEAD_FINDER_INPUT in the Agency Lead Finder's "businesses" input format (one entry per website domain; businesses without a website are kept as no-website leads). You can also give that Actor this run's ID or dataset ID directly.

## Actor input object example

```json
{
  "queries": [
    "cafes in Plymouth"
  ],
  "source": "osm",
  "maxResultsPerQuery": 200,
  "maxResultsTotal": 5000,
  "countryCode": "gb",
  "radiusMeters": 2000,
  "minRadiusMeters": 2000,
  "tileSizeKm": 2,
  "maxTilesPerQuery": 40,
  "maxSplitDepth": 3,
  "osmTileSizeKm": 10,
  "googleFieldPreset": "contact",
  "googleBudgetUsd": 5,
  "googleDryRun": false,
  "googleSearchMode": "text",
  "googleStrictTypeFiltering": false,
  "googleIncludeServiceAreaBusinesses": false,
  "includeReviews": false,
  "includeReviewerNames": false,
  "geocoder": "nominatim",
  "googleMaxConcurrency": 3,
  "languageCode": "en",
  "hasWebsite": false,
  "ownWebsiteOnly": false,
  "noWebsiteOnly": false,
  "hasPhone": false,
  "openNow": false,
  "operationalOnly": true,
  "includeEmails": false,
  "overpassUrl": "https://overpass-api.de/api/interpreter",
  "nominatimUrl": "https://nominatim.openstreetmap.org",
  "agencyLeadFinderExport": false
}
```

# Actor output Schema

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

No description

## `files` (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 = {
    "queries": [
        "cafes in Plymouth"
    ],
    "countryCode": "gb"
};

// Run the Actor and wait for it to finish
const run = await client.actor("siteverdict/siteverdict-local-business-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 = {
    "queries": ["cafes in Plymouth"],
    "countryCode": "gb",
}

# Run the Actor and wait for it to finish
run = client.actor("siteverdict/siteverdict-local-business-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 '{
  "queries": [
    "cafes in Plymouth"
  ],
  "countryCode": "gb"
}' |
apify call siteverdict/siteverdict-local-business-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,siteverdict/siteverdict-local-business-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/pcBMBYolQ6ljF6Gdf/builds/3RAS3OtfECvhz8gnn/openapi.json
