Use cases / Google Maps API, Website Contacts API
Local lead generation
Last updated September 28, 2026. Response excerpts are recorded from the live API.
Who
Who runs this, and what for
| Team | What they decide with it |
|---|---|
| Agencies and B2B sales teams | Which local businesses to call, with a list that has phones and websites already. |
| Vertical SaaS companies | How many prospects a region holds, and which have no website or few reviews. |
| Marketplaces | Which providers to invite onto the platform, city by city. |
Pipeline
The calls, step by step
01Search each category and city
One Maps search scrolls the results list and returns every place it loaded, usually 20 to 70, with the phone, website, address, category, rating, and review count. place_id identifies each business across searches.
GET /v1/search?engine=google_maps&q=plumbers+in+Denver&country=us · results.0
{ "position": 1, "title": "Applewood Plumbing Heating & Electric", "place_id": "ChIJkWUJx22Ha4cRVVLq4AJk7Ng", "data_id": "0x876b876dc7096591:0xd8ec6402e0ea5255", "cid": "15630978370222314069", "latitude": 39.7583004, "longitude": -105.0512657, "rating": 4.8, "reviews_count": 17793, "category": "Plumber", "price": null, "address": "5000 W 29th Ave", "open_state": "Open · Closes 10 PM", "phone": "(303) 444-1633", "website": "https://www.applewoodfixit.com/", "description": null, "wheelchair_accessible": null, "action_links": [ { "label": "Book online", "url": "https://book.servicetitan.com/lhxa271djh03w5bfje81s0qd" } ], "thumbnail": null, "sponsored": false, "url": "https://www.google.com/maps/place/?q=place_id:ChIJkWUJx22Ha4cRVVLq4AJk7Ng" }Recorded response. Live values differ. 02Deduplicate and filter
Neighbouring cities return some of the same businesses, and sponsored listings repeat organic ones. Keep one row per place_id, drop sponsored, and filter on category, rating, or whether there is a website.
GET /v1/search?engine=google_maps&q=plumbers+in+Denver&country=us · results.0.place_id
"ChIJkWUJx22Ha4cRVVLq4AJk7Ng"Recorded response. Live values differ. 03Add the email from the business's website
Maps has phones but no emails. The Website Contacts API reads the business's own site and returns the addresses it publishes, marking which are on its own domain, plus social profiles and a phone in E.164.
GET /v1/contacts?domain=stripe.com · emails
[ { "value": "privacy@stripe.com", "type": "generic", "domain_match": true, "sources": [ "https://stripe.com/privacy" ] } ]Recorded response. Live values differ.
Code
A script to start from
local_leads.py
# Local lead list: every business Google Maps lists for a category in each
# city, enriched with the email the business publishes on its website.
import csv, os, requests
from urllib.parse import urlparse
API = "https://api.clair.im"
HEADERS = {"Authorization": f"Bearer {os.environ['CLAIR_API_KEY']}"}
CATEGORY = "plumbers"
CITIES = ["Denver, CO", "Aurora, CO", "Boulder, CO"]
def get(path, **params):
r = requests.get(f"{API}{path}", headers=HEADERS, timeout=120, params=params)
r.raise_for_status()
return r.json()
seen = set()
with open("leads.csv", "w", newline="") as f:
out = csv.writer(f)
out.writerow(["name", "category", "phone", "website", "email", "address",
"rating", "reviews", "place_id"])
for city in CITIES:
body = get("/v1/search", engine="google_maps", q=f"{CATEGORY} in {city}")
for p in body["results"]:
if p["place_id"] in seen or p["sponsored"]:
continue # neighbouring cities overlap; ads repeat organic listings
seen.add(p["place_id"])
email = None
if p["website"]:
try:
site = get("/v1/contacts", domain=urlparse(p["website"]).hostname, pages=3)
email = next((e["value"] for e in site["emails"] if e["domain_match"]), None)
except requests.HTTPError:
pass # site down or blocking; keep the Maps data
out.writerow([p["title"], p["category"], p["phone"], p["website"], email,
p["address"], p["rating"], p["reviews_count"], p["place_id"]])
Set CLAIR_API_KEY to a key subscribed to the Google Maps API and Website Contacts API. Each call is one request against that API's monthly quota.
Cost
What it costs per month
| Schedule | Requests | Plan | Per month | Per 1,000 |
|---|---|---|---|---|
| 1 category × 20 cities, 600 websites20 searches, then about 30 unique businesses with a website per city = 600 lookups. | 620 | Pay as you go + Pay as you go | $12.46 | $20.10 |
| 5 categories × 50 cities250 searches, about 30 websites each = 7,500 lookups. | 7,750 | Pay as you go + Pro | $106.29 | $13.71 |
| Maps only, 5 categories × 200 cities1,000 searches with phones and websites, no email step. | 1,000 | Pay as you go | $20.10 | $20.10 |
Limits
What to plan for
- A Maps search covers the area the map shows. For a large city, split it by neighbourhood, or pass latitude, longitude, and zoom for each part.
- Google sometimes gives signed-out visitors a limited view of a place. Search results still have phone and website; place details may be missing, and place.view says so.
- Many small businesses publish only a contact form, so expect an email for a share of websites, not all. The phone from Maps covers the rest.
- Check the rules for cold outreach where you sell. Published business contacts are still personal data in some jurisdictions.
FAQ
Common questions
How many businesses does one search return?
Every place the Maps list loaded, usually 20 to 70. Google stops loading more at some point, so a dense category in a big city needs several smaller searches.
Why not use Google's Places API?
You can: it is official and has a free monthly allowance. It returns at most 20 places per page and charges more for phone and website fields; the Google Places comparison has the numbers.
Are the emails verified?
They are published on the business's site, and sources says on which page. Clair does not send test email; run a verifier before a large campaign.
Run it on your own products
200 requests a month free on each API, no card. Enough to run the pipeline on a short list before choosing a plan.
Bloomberg News API
Associated Press API
Glassdoor API
Greenhouse Jobs API

Crunchbase API
Website Contacts API