Google Maps Scraper | $1/1k Places avatar

Google Maps Scraper | $1/1k Places

Pricing

from $0.75 / 1,000 place scrapeds

Go to Apify Store
Google Maps Scraper | $1/1k Places

Google Maps Scraper | $1/1k Places

Scrape Google Maps for $1/1k places: 30+ fields, reviews, photos, hours, GPS. Enrich every result with 20+ optional add-ons: emails, phones, socials, LinkedIn, SSL, tech stack, SEO, AI contacts, jobs & icebreakers. All merged into ONE row per business. No dataset joins. MCP-ready for AI agents.

Pricing

from $0.75 / 1,000 place scrapeds

Rating

5.0

(3)

Developer

NanoScrape

NanoScrape

Maintained by Community

Actor stats

9

Bookmarked

674

Total users

249

Monthly active users

8 hours

Issues response

18 hours ago

Last modified

Share

Google Maps Scraper: businesses, reviews, popular times, contacts, jobs

Scrape Google Maps search results anywhere in the world. You get business names, addresses, phone numbers, websites, reviews, ratings, opening hours, GPS coordinates, and 30+ structured fields per place. Fast, reliable, and priced per result.

Optionally chain enrichment on every website found: emails and phones via a regex extractor (no API key), or AI-powered contacts and job listings using your own Gemini, Groq, or OpenRouter key. All merged into a single dataset row per business.

Why use this scraper

  • Simple pay-per-result pricing. $1.00 per 1,000 places on the Free plan (down to $0.75/1,000 on Business Gold). Reviews are billed separately at $0.30 per 1,000 reviews only when you opt in with includeReviews: true. All other 30+ fields per place (photos, hours, address parsing, categories, popular_times when enabled, etc.) are included in the place price.
  • Fast. Results in seconds, not minutes.
  • Volume-friendly. $1.00 per 1,000 places.
  • Built-in enrichment. One input flag adds emails, phones, socials, contacts, or job listings from each business's website.
  • Bring your own LLM key. Use Gemini (recommended, free tier available), Groq, or OpenRouter. Automatic fallback between providers when you supply multiple keys.
  • Works worldwide. Any country, any language. Pass an ISO language code and get localized results.

Use with AI agents (MCP)

Connect this actor to any MCP-compatible AI client: Claude Desktop, Claude.ai, Cursor, VS Code, LangChain, LlamaIndex, or a custom agent.

MCP server URL:

https://mcp.apify.com?tools=santamaria-automations/google-maps-scraper

Example prompt once connected:

"Use google-maps-scraper to find pediatric dentists in Zurich and put the results in a table."

Clients that support dynamic tool discovery (Claude.ai, VS Code) will receive the full input schema automatically via add-actor.

Quick start

Pass plain search strings. Include the location in the string itself:

{
"searchStrings": [
"restaurants in Paris",
"coffee shops in Tokyo",
"dentists in New York"
],
"maxResultsPerQuery": 20
}

Structured queries

Use queries when you want to attach your own company_id for join-back, or split the search term from the location:

{
"queries": [
{
"query": "software companies",
"location": "Berlin",
"country": "DE",
"company_id": "my-internal-id-123"
},
{
"query": "plumbers",
"location": "London",
"country": "GB"
}
],
"maxResultsPerQuery": 50,
"language": "en"
}

The email extractor pulls contact details straight from each place's website using regex. No LLM key needed, no AI cost.

{
"searchStrings": ["altenpflegeheim konstanz"],
"maxResultsPerQuery": 20,
"enableEmailExtraction": true
}

Each place row gets four new fields: emails_from_website, phones_from_website, social_urls, and address_from_website.

Add AI-powered contacts and job listings

Bring your own LLM API key. Structured contacts (name, position, email, phone) and job listings get merged into the same dataset row as the Maps data.

{
"searchStrings": ["IT companies in Munich"],
"maxResultsPerQuery": 20,
"enableContactExtraction": true,
"enableJobExtraction": true,
"enableEmailExtraction": true,
"groqApiKey": "gsk_your_own_key",
"waitForAddOns": true
}

The scraper waits for each add-on to finish and merges the results into each place row. You end up with one row per business, everything in the same dataset. That's the flow that works nicely for n8n, Zapier, or Make workflows where you don't want to join multiple datasets.

Add website health signals and personalized icebreakers

Turn on the full stack: contact enrichment, website health, and a personalized cold-outreach opening line per place.

{
"searchStrings": ["care homes in konstanz"],
"maxResultsPerQuery": 20,
"enableEmailExtraction": true,
"enableSslInspection": true,
"enableWhoisDns": true,
"enableTechStack": true,
"enableSeoAudit": true,
"enableBrokenLinkCheck": true,
"enableFacebookScrape": true,
"enablePhoneValidation": true,
"enableEmailVerification": true,
"enableAiIcebreaker": true,
"icebreakerTone": "friendly",
"icebreakerFocus": "Mention that we specialize in local SEO for care homes",
"outputLanguage": "de",
"geminiApiKey": "your_gemini_key",
"waitForAddOns": true
}

Each place row now carries: SSL status, WHOIS registrar and domain age, MX provider, tech stack, broken-link count, GBP completeness score, phone validation, Facebook page data (including runs_ads), verified emails, and one German-language icebreaker line ready to paste into a cold email.

Input parameters

ParameterTypeDefaultDescription
searchStringsstring[][]Simple search strings. Include the location in the string for geo-targeting.
queriesobject[][]Structured queries with optional query, location, country, company_id.
maxResultsPerQueryinteger20Results per query. Max 120 (Google Maps page limit).
maxTotalResultsinteger(none)Optional global cap across all queries combined. Leave empty for no cap. Useful for budget control.
maxResultsinteger(none)Deprecated alias for maxResultsPerQuery. Still works for backward compatibility.
languagestring"en"ISO 639-1 language code (en, de, fr, ja, es, pt, it, ko, zh, ar, etc.).
includeReviewCountbooleantrueEnrich each result with review_count via the Maps Embed API. Adds about 150 ms per place.
includePopularTimesbooleanfalseOpt-in. Enrich each result with the visitor-traffic histogram by weekday × hour (0–100). Adds about 1.5 s per place.
includeReviewsbooleanfalseOpt-in. Fetch featured reviews for each place (author, rating, date, text). Adds about 300 ms per place.
maxReviewsPerPlaceinteger25Maximum reviews per place. Set to 0 for no cap.
reviewsSortstringmost_relevantSort order: most_relevant, newest, highest_rating, lowest_rating.
reviewsNewerThanstring(none)Only return reviews on or after this date (YYYY-MM-DD). Auto-switches sort to newest for an efficient cutoff.
enableGbpCompletenessbooleanfalseZero-fetch. Scores every place's Google Business Profile 0-100 across 8 signals and flags unclaimed / website-less listings as HIGH priority. Runs as a separate billed actor.
enablePhoneValidationbooleanfalseFor every place with a phone, validate and enrich with country, line type, carrier, and E.164 / international / national formats.
enableEmailExtractionbooleanfalseRegex extractor for emails, phone numbers, and social URLs (LinkedIn, Facebook, Instagram, Twitter, YouTube). No LLM key required. Also decodes Cloudflare Email Protection markup.
enableEmailVerificationbooleanfalseVerify every discovered email: syntax, MX lookup, disposable check, role-account detection, spam-trap and catch-all scoring. Adds a verified_emails array to each place row (one entry per discovered email with status / score / disposable / role_account / mx_provider). Auto-enables enableEmailExtraction.
enableFacebookScrapebooleanfalseScrape each place's Facebook page (followers, likes, category, contact info, runs_ads). Auto-enables enableEmailExtraction (the FB URL is discovered there). Sub-actor forces residential proxy.
enableLinkedInCompanyScrapebooleanfalseScrape each place's LinkedIn Company page (follower count, employee count, industry, headquarters, founded, specialties, up to 5 decision-maker employees, up to 5 recent posts). Handles legacy numeric-ID URLs. Auto-enables enableEmailExtraction.
enableTwitterScrapebooleanfalseScrape each place's Twitter/X profile (followers, tweet count, bio, location, verified, blue-verified, up to 5 recent tweets with engagement). Auto-enables enableEmailExtraction. Sub-actor forces residential proxy (X rate-limits datacenter IPs).
enableInstagramScrapebooleanfalseScrape each place's Instagram profile (followers, following, posts count, biography, business category, up to 6 recent posts). Auto-enables enableEmailExtraction.
enableTikTokScrapebooleanfalseScrape each place's TikTok profile (followers, hearts, video count, verified, signature, up to 6 recent videos with views / likes / comments / shares). Auto-enables enableEmailExtraction.
tikTokMaxVideosPerProfileinteger6How many recent videos to include per TikTok profile (0–20). 0 disables recent-video capture and returns profile stats only. Only used when enableTikTokScrape is true.
enableYouTubeChannelScrapebooleanfalseScrape each place's YouTube channel (subscribers, video count, handle, up to 6 recent uploads with views + thumbnails). Legacy /user/ and /c/ URLs auto-normalized to /@handle. Auto-enables enableEmailExtraction.
enableSslInspectionbooleanfalseInspect the SSL/TLS certificate on each website. Returns issuer, expiry, days_until_expiry, and a flag (ok / expiring_soon / expired / self_signed / invalid).
enableWhoisDnsbooleanfalseWHOIS (registrar, domain age) + DNS (A / MX / TXT / NS, has_spf, has_dmarc, has_dkim, email_provider, mx_provider) for each domain.
enableSeoAuditbooleanfalseSEO signals from every homepage: title, meta description (with lengths), canonical URL, robots directive, HTML lang, and Open Graph tags.
enableTechStackbooleanfalseDetect CMS, frameworks, analytics, servers, CDN via a Wappalyzer-style pattern set. Adds a technologies list plus a category summary.
enableBrokenLinkCheckbooleanfalseCrawl the homepage plus one hop of internal links (up to 15 URLs) and flag broken links (404 / 410 / 5xx / timeouts / DNS / SSL). Cloudflare/WAF 403 false positives are surfaced separately under possibly_protected_count and don't degrade site_health_flag.
enablePageSpeedAuditbooleanfalseFor every website, call Google's PageSpeed Insights API v5 for Lighthouse Performance + Accessibility + Best Practices + SEO scores plus Core Web Vitals (LCP, CLS, FCP, TBT, SI). Requires googleApiKey. Adds page_speed_audit to each row.
googleApiKeystring(none)Your Google Cloud API key with the PageSpeed Insights API enabled. Only used by enablePageSpeedAudit. Free tier: 25,000 queries/day per Google Cloud project. Not an LLM key. Step-by-step setup guide in the PageSpeed section below.
pageSpeedStrategystring"mobile"mobile or desktop. Passed straight to PageSpeed Insights.
enableJobBoardPresencebooleanfalseLook up open roles on LinkedIn Jobs per place and return aggregate hiring signals (open_roles_count, senior_roles_count, sample_titles, top_industry, hiring_signal). Prefers the LinkedIn company URL; falls back to company-name search. Adds job_boards.linkedin_jobs to each row.
enableComplianceScanbooleanfalseFetch each place's homepage and detect legal/compliance signals: imprint/impressum page, privacy policy, cookie banner, GDPR/DSGVO mentions, GOV/ministry footer badges, business registration numbers. Great DACH lead-quality filter — flags shady or incomplete presences. Adds compliance to each row. Runs as a separate sub-actor.
enableFreshnessCheckbooleanfalseProbe each place's website for freshness signals: sitemap <lastmod>, homepage Last-Modified header, footer copyright year, and RSS/Atom feed pubDate. Picks the most-recent signal and returns a stale_flag verdict (active / stale / abandoned / unknown). Perfect web-design agency pitch angle. Adds freshness to each row. Runs as a separate sub-actor.
enableReviewIntelligencebooleanfalseLLM-powered review analysis: overall sentiment (strongly_positive / positive / mixed / negative / strongly_negative), sentiment score, top complaint_themes[] and praise_themes[] with verbatim quotes, owner_tone, red_flags[], and opportunity_signals[] across all fetched reviews. Auto-enables includeReviews. Requires your own LLM key. Adds review_intel_llm to each row.
enableContactExtractionbooleanfalseAI-powered structured contact extraction (name, position, email, phone, LinkedIn). Requires your own LLM API key.
enableJobExtractionbooleanfalseAI-powered job-listing extraction from careers pages. Requires your own LLM API key.
enableAiIcebreakerbooleanfalseGenerate a personalized cold-outreach opening line for every place. Requires your own LLM API key.
enableIcebreakerEnrichedbooleanfalseWhen AI icebreaker is on, also fetch each place's homepage for a richer prompt. Higher cost, higher quality.
icebreakerTonestring"friendly"Voice of the icebreaker line: friendly, professional, casual, direct.
icebreakerFocusstring(none)Optional free-text guidance for the icebreaker prompt (e.g. "focus on their photos", "mention we do local SEO for care homes").
enableBrowserFallbackbooleanfalseRe-scrape JS-rendered sites (React, Vue, Angular) with a full browser. Catches about 25% more sites.
outputLanguagestring"en"Language for AI-generated text fields (contacts, jobs, icebreaker line). Accepts any ISO 639-1 code (see Supported languages below) or auto to match the website.
geminiApiKeystring(none)Your Gemini API key. Free tier is generous, so this is the recommended starting point.
groqApiKeystring(none)Your Groq API key. Ultra-fast inference with a free tier.
openrouterApiKeystring(none)Your OpenRouter API key. Access to 100+ models. Optional fallback.
llmModelstring(provider default)Optional model override. Useful when your OpenRouter key doesn't have access to the default (google/gemini-2.0-flash-001) and you need to point at a free model like meta-llama/llama-3.1-8b-instruct. Also works for Gemini and Groq.
waitForAddOnsbooleantrueWhen true (default): the scraper waits for each add-on and merges its output into the same dataset row as the Maps result. When false: add-ons run fire-and-forget and write to their own datasets, and you handle the join yourself.
addOnWaitCapMsinteger1800000Safety cap on how long to wait for add-ons before flushing remaining places with a timed_out status. Default 30 min.
excludeCidsstring[][]Google CIDs to skip. Useful for excluding already-scraped places.
maxConcurrencyinteger10Parallel requests (1–20).
requestDelayinteger300Delay between pagination requests in milliseconds (100–10,000).
proxyConfigurationobjectApify proxyProxy settings for the main Maps crawl. Datacenter proxies work well and are cost-efficient for gmaps itself. Note: the Facebook, Twitter/X, and LinkedIn Company sub-actors override this and force residential proxies internally (their targets rate-limit or wall off datacenter IPs). Per-result cost is higher for those three add-ons.

Use either searchStrings or queries, or both. searchStrings sends each string directly to Google Maps. queries lets you separate the search term from the location and attach your own company_id for linking results back to your data.

About the LLM API key fields. They're shown as plain text (not masked) in the Apify UI. That's a technical necessity for how Apify forwards fields to sub-actors right now: secret-marked fields get encrypted on the parent side and the sub-actor can't decrypt them. If the visibility bothers you, use a scoped or throwaway key.

Output

Each result has 30+ fields. Here's a shortened example (fictional restaurant, dummy names and identifiers used throughout the docs for privacy):

{
"company_id": "restaurants in Paris",
"title": "Example Bistro",
"category": "French restaurant",
"categories": ["French restaurant", "Restaurant"],
"address": "1 Example Street, 75000 Paris, France",
"complete_address": {
"street": "1 Example Street",
"city": "Paris",
"postal_code": "75000",
"state": "Ile-de-France",
"country": "France"
},
"latitude": 48.8571,
"longitude": 2.3007,
"plus_code": "8FW4V87C+X3",
"timezone": "Europe/Paris",
"phone": "+33 1 00 00 00 00",
"website": "https://www.example.com/",
"review_rating": 4.5,
"review_count": 1823,
"reviews_per_rating": { "1": 42, "2": 31, "3": 89, "4": 312, "5": 1349 },
"reviews_link": "https://search.google.com/local/reviews?placeid=...",
"user_reviews": [
{
"name": "Jane Doe",
"profile_link": "https://www.google.com/maps/contrib/...",
"rating": 5,
"text": "Excellent cuisine, warm atmosphere.",
"published_at": "2025-12-10",
"response_from_owner": null
}
],
"status": "OPERATIONAL",
"price_range": "$$",
"open_hours": {
"Monday": ["12:00-14:30", "19:00-22:30"],
"Tuesday": ["12:00-14:30", "19:00-22:30"],
"Wednesday": ["12:00-14:30", "19:00-22:30"],
"Thursday": ["12:00-14:30", "19:00-22:30"],
"Friday": ["12:00-14:30", "19:00-23:00"],
"Saturday": ["12:00-23:00"],
"Sunday": []
},
"images": [
{ "title": "Interior", "image": "https://lh3.googleusercontent.com/..." }
],
"owner": { "name": "Example Bistro", "link": "https://maps.google.com/..." },
"reservations": [
{ "link": "https://www.example-reservations.com/...", "source": "Example Reservations" }
],
"order_online": [],
"menu": { "link": "https://www.example.com/menu", "source": "example.com" },
"description": "Cozy French bistro in the heart of the 7th arrondissement.",
"link": "https://www.google.com/maps/place/Example+Bistro/...",
"cid": "12345678901234567",
"data_id": "0x47e671a4e...",
"searchString": "restaurants in Paris",
"_rank": 1,
"scraped_at": "2026-03-05T10:30:00.000Z"
}

Merged add-on output

When you enable add-ons with waitForAddOns: true, the same row gets the following extra fields merged in. Each block only appears when its add-on is enabled and returned data. Every place record also carries _enrichment_status with a per-add-on outcome (success, no_data, failed, timed_out, or skipped).

{
"emails_from_website": ["info@example.com", "reservations@example.com"],
"phones_from_website": ["+33 1 00 00 00 00"],
"social_urls": {
"linkedin": "https://www.linkedin.com/company/example-bistro",
"facebook": "https://www.facebook.com/examplebistro",
"instagram": "https://www.instagram.com/examplebistro",
"twitter": "https://x.com/example_bistro",
"tiktok": "https://www.tiktok.com/@example_bistro",
"youtube": "https://www.youtube.com/@example_bistro"
},
"address_from_website": "1 Example Street, 75000 Paris",
"contacts": [
{
"name": "Jane Doe",
"firstname": "Jane",
"lastname": "Doe",
"salutation": "Ms.",
"position": "Head Chef",
"department": "Kitchen",
"email": "dummy@example.com",
"phone": "+33 1 00 00 00 01",
"linkedin_url": "https://www.linkedin.com/in/janedoe-example",
"source_url": "https://www.example.com/team"
}
],
"job_listings": [
{
"title": "Sous Chef",
"location": "Paris",
"department": "Kitchen",
"employment_type": "Full-time",
"job_url": "https://example.jobs.personio.de/job/12345",
"application_url": "https://example.jobs.personio.de/apply/12345",
"posted_at": "2026-07-01",
"ats_system": "personio"
}
],
"verified_emails": [
{
"email": "info@example.com",
"status": "valid",
"syntax_ok": true,
"mx_ok": true,
"mx_provider": "google_workspace",
"disposable": false,
"role_account": true,
"score": 82
}
],
"phone_validation": {
"valid": true,
"number_type": "FIXED_LINE",
"country_code": 33,
"country_iso": "FR",
"country_name": "France",
"e164": "+330100000000",
"international": "+33 1 00 00 00 00",
"national": "01 00 00 00 00",
"location": "Paris"
},
"facebook": {
"page_url": "https://www.facebook.com/examplebistro",
"page_name": "Example Bistro",
"followers": 2174,
"likes": 2174,
"email": "info@example.com",
"phone": "+33 1 00 00 00 00",
"category": "French Restaurant",
"rating": 4.5,
"runs_ads": true,
"about": "Cozy French bistro in the heart of Paris.",
"entity_match_confidence": "high"
},
"linkedin_company": {
"linkedin_url": "https://www.linkedin.com/company/example-bistro",
"company_name": "Example Bistro",
"industry": "Restaurants",
"description": "Cozy French bistro serving seasonal dishes in Paris since 2007.",
"headquarters": "Paris, France",
"website": "https://www.example.com/",
"founded": "2007",
"company_type": "Privately Held",
"follower_count": 1240,
"employee_count": 12,
"employee_count_range": "11-50",
"specialties": ["French cuisine", "Wine pairing", "Private events"],
"address": "1 Example Street, 75000 Paris, France",
"employees": [
{"name": "Jean Dupont", "title": "Owner and Head Chef", "profile_url": "https://www.linkedin.com/in/jean-dupont-example", "location": "Paris, France", "avatar_url": ""}
],
"recent_posts": [
{"text": "Fresh truffle season starts this week at Example Bistro.", "posted_at": "3d", "likes": 42, "comments": 5, "shares": 2}
],
"resolved_from_numeric_id": false,
"entity_match_confidence": "high"
},
"twitter": {
"profile_url": "https://x.com/example_bistro",
"username": "example_bistro",
"display_name": "Example Bistro",
"bio": "Cozy French bistro in Paris. Reservations required. #ParisianCuisine",
"location": "Paris, France",
"website": "https://www.example.com/",
"verified": false,
"blue_verified": true,
"followers_count": 1842,
"following_count": 210,
"tweet_count": 3120,
"media_count": 640,
"created_at": "2014-05-12",
"entity_match_confidence": "high"
},
"instagram": {
"profile_url": "https://www.instagram.com/example_bistro",
"username": "example_bistro",
"full_name": "Example Bistro",
"biography": "Cozy French bistro in the 7th arrondissement.",
"external_url": "https://www.example.com/",
"is_verified": false,
"is_business": true,
"category": "French Restaurant",
"followers": 8420,
"following": 512,
"posts_count": 640,
"entity_match_confidence": "medium"
},
"tiktok": {
"profile_url": "https://www.tiktok.com/@example_bistro",
"username": "example_bistro",
"nickname": "Example Bistro",
"signature": "Cozy French bistro in Paris.",
"verified": true,
"followers": 4200,
"following": 48,
"hearts": 150000,
"video_count": 92,
"entity_match_confidence": "high"
},
"youtube": {
"channel_url": "https://www.youtube.com/@example_bistro",
"channel_id": "UC1234567890abcdefghijklmn",
"channel_handle": "@example_bistro",
"channel_name": "Example Bistro",
"subscribers": "12.4K",
"video_count": 148,
"latest_videos": [
{"video_id": "dQw4w9WgXcQ", "title": "Behind the pass on a Friday night", "published_at": "2026-07-03T18:41:00Z", "view_count": 4820, "thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg", "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}
],
"entity_match_confidence": "medium"
},
"ssl": {
"valid": true,
"issuer": "Sectigo Limited",
"valid_from": "2025-10-09T00:00:00Z",
"valid_to": "2026-10-23T23:59:59Z",
"days_until_expiry": 98,
"is_expired": false,
"matches_hostname": true,
"flag": "ok"
},
"whois": {
"registrar": "Example Registrar SE",
"created_date": "2020-11-13T15:34:08Z",
"domain_age": "5 years",
"mx_provider": "Google Workspace",
"has_spf": true,
"has_dmarc": true,
"has_dkim": false,
"nameservers": [
"ns1.example-dns.net",
"ns2.example-dns.net"
],
"email_provider": "Google Workspace"
},
"seo_audit": {
"status_code": 200,
"title": "Example Bistro: French cuisine in Paris",
"title_length": 39,
"meta_description": "Cozy French bistro serving seasonal dishes in the 7th arrondissement. Reservations recommended.",
"meta_description_length": 96,
"canonical_url": "https://www.example.com/",
"robots": "index, follow, max-image-preview:large",
"lang": "fr-FR",
"og_title": "Example Bistro: French cuisine in Paris",
"og_description": "Cozy French bistro serving seasonal dishes.",
"final_url": "https://www.example.com/"
},
"tech_stack": {
"technologies": [
{"name": "WordPress", "categories": ["CMS", "Blogs"], "confidence": 100},
{"name": "Yoast SEO", "categories": ["SEO"], "confidence": 100},
{"name": "Google Tag Manager", "categories": ["Tag managers"], "confidence": 100},
{"name": "PHP", "categories": ["Programming languages"], "confidence": 50}
],
"category_summary": {
"cms": ["WordPress"],
"seo": ["Yoast SEO"],
"tag_managers": ["Google Tag Manager"],
"programming_languages": ["PHP"]
},
"status_code": 200,
"final_url": "https://www.example.com/"
},
"broken_links": {
"total_checked": 15,
"broken_count": 0,
"possibly_protected_count": 0,
"site_health_flag": "healthy",
"broken_links": [],
"possibly_protected_links": [],
"final_url": "https://www.example.com/"
},
"page_speed_audit": {
"strategy": "mobile",
"final_url": "https://www.example.com/",
"psi_status": "success",
"performance_score": 62,
"accessibility_score": 88,
"best_practices_score": 92,
"seo_score": 100,
"performance_flag": "average",
"accessibility_flag": "good",
"lcp_ms": 2840,
"cls": 0.08,
"fcp_ms": 1610,
"tbt_ms": 320,
"si_ms": 3120
},
"gbp_completeness": {
"score": 88,
"denominator": 8,
"signals_observed": ["has_website", "has_phone", "has_hours", "has_description", "has_category", "photo_count", "review_count", "is_claimed"],
"sub_scores": {
"has_website": 1, "has_phone": 1, "has_hours": 1, "has_description": 1,
"has_category": 1, "photo_count": 1, "review_count": 1, "is_claimed": 0.5
},
"missing_fields": ["is_claimed"],
"lead_priority_flag": "NORMAL"
},
"compliance": {
"site_language": "fr",
"has_impressum": null,
"impressum_url": "",
"has_privacy_policy": true,
"privacy_policy_url": "https://www.example.com/mentions-legales",
"cmp_detected": true,
"cookie_consent_status": "detected",
"consent_tool": "cookiebot",
"sets_tracking_cookies": false,
"risk_flag": "LOW",
"risk_reasons": [],
"final_url": "https://www.example.com/",
"status_code": 200
},
"freshness": {
"sitemap_url": "https://www.example.com/sitemap.xml",
"sitemap_lastmod": "2026-06-14",
"homepage_last_modified": "2026-06-14",
"footer_copyright_year": 2026,
"latest_feed_pubdate": null,
"most_recent_signal": "2026-06-14",
"most_recent_source": "sitemap",
"stale_flag": "active",
"months_since_last_signal": 1.3,
"signals_found": 3,
"final_url": "https://www.example.com/",
"status_code": 200
},
"icebreaker": {
"line": "Noticed you have been running one of Paris's highest-rated bistros (4.5★, 1,823 reviews) since the early 2000s. Curious how you keep the reservations coming week after week.",
"enriched": true,
"llm_provider": "gemini",
"llm_model": "gemini-2.0-flash"
},
"review_intel_llm": {
"overall_sentiment": "positive",
"sentiment_score": 0.7,
"complaint_themes": [{"theme": "slow service", "frequency": 2, "sample_quote": "waited 40 min for our mains"}],
"praise_themes": [{"theme": "delicious food", "frequency": 9, "sample_quote": "the truffle pasta was incredible"}],
"owner_tone": "professional",
"red_flags": [],
"opportunity_signals": ["high_repeat_customer"],
"llm_provider": "groq",
"llm_model": "llama-3.3-70b-versatile"
},
"review_intel_math": {
"rating_trend": "stable",
"rating_trend_delta": 0.05,
"review_velocity": 3.5,
"owner_response_rate": 0.72,
"months_since_last_review": 1.2,
"freshness_flag": "active",
"reviews_analyzed": 25
},
"job_boards": {
"linkedin_jobs": {
"linkedin_company_id": "12345678",
"linkedin_jobs_url": "https://www.linkedin.com/company/example-bistro/jobs/",
"search_url": "https://www.linkedin.com/jobs/search/?f_C=12345678&location=Worldwide",
"open_roles_count": 3,
"senior_roles_count": 1,
"avg_applicants": 12.0,
"sample_titles": ["Sous Chef", "Restaurant Manager", "Sommelier"],
"sample_locations": ["Paris, France"],
"sample_seniorities": ["Mid-Senior level", "Entry level"],
"sample_remote_options": ["On-site"],
"top_industry": "Hospitality",
"hiring_signal": "active",
"match_strategy": "company_id"
}
},
"chain": {
"is_chain": false
},
"local_rank": {
"position_in_category": 4,
"cohort_size": 18,
"review_share_pct": 8.2,
"top3_gap": 210,
"rating_gap": 0.15,
"category_key": "french restaurant|paris",
"confidence": "high"
},
"reservation_platforms": ["thefork"],
"duplicate_of": null,
"dedupe_confidence": null,
"_enrichment_status": {
"emails": "success",
"email_verification": "success",
"contacts": "success",
"jobs": "success",
"gbp": "success",
"phone_validation": "success",
"facebook": "success",
"linkedin_company": "success",
"twitter": "success",
"instagram": "success",
"tiktok": "success",
"youtube": "success",
"ssl": "success",
"whois": "success",
"seo": "success",
"tech": "success",
"broken_links": "success",
"page_speed": "success",
"compliance": "success",
"freshness": "success",
"icebreaker": "success",
"review_intel": "success",
"job_boards": "success"
}
}

The Email Verification add-on merges a verified_emails array onto every place row that had discovered emails (one entry per email with status / score / disposable / role_account / mx_provider). See Email verification for the full field breakdown.

Complete field reference

FieldTypeDescription
company_idstringYour reference ID (from queries) or the search string
place_idstringGoogle Place ID — the canonical, stable identifier for this business across Google's APIs. Use this as the merge key when joining gmaps output to other data sources.
titlestringBusiness name
categorystringPrimary Google Maps category
categoriesstring[]All assigned categories
addressstringFull formatted address
complete_addressobjectParsed components: street, city, postal_code, state, country
latitudenumberGPS latitude
longitudenumberGPS longitude
plus_codestringGoogle Plus Code
timezonestringIANA timezone identifier
phonestringPhone number
websitestringWebsite URL
open_hoursobjectOpening hours grouped by day of week
popular_timesobjectVisitor traffic patterns (opt-in via includePopularTimes)
price_rangestringPrice level ($ to $$$$)
statusstringOPERATIONAL, CLOSED_TEMPORARILY, or CLOSED_PERMANENTLY
review_countintegerTotal number of reviews
review_ratingnumberAverage rating (1.0 to 5.0). rating is a duplicate alias — pick one, treat the other as deprecated.
ratingnumberAlias for review_rating (present for backward compatibility). Prefer review_rating in new integrations.
reviews_per_ratingobjectBreakdown of reviews by star rating (1 through 5)
reviews_linkstringDirect URL to all reviews
user_reviewsarraySample reviews with name, rating, text, date, owner response
imagesarrayAdditional photos with titles
reservationsarrayReservation links with source (TheFork, OpenTable, etc.)
order_onlinearrayOnline ordering links with source
menuobjectMenu link and source
ownerobjectBusiness owner name and profile link
descriptionstringBusiness description from Google
linkstringFull Google Maps URL for this place
cidstringGoogle Maps CID: a stable identifier that persists across name and address changes
data_idstringGoogle internal data ID
searchStringstringThe search query that produced this result
_rankintegerPosition in search results (1-based)
scraped_atstringISO 8601 timestamp
emails_from_websitestring[]Emails extracted from the company's website. Placeholder addresses (info@domain.com, you@yourdomain.com, RFC 6761 reserved TLDs) filtered. Add-on: email extractor
phones_from_websitestring[]Phone numbers extracted from the company's website
verified_emailsobject[]Per-email verification records: email, status (valid / risky / dead), score (0-100), syntax_ok, mx_ok, mx_provider, disposable, role_account, catch_all. Add-on: email verification
social_urlsobjectSocial profiles from the website: linkedin, facebook, instagram, twitter, tiktok, youtube
address_from_websitestringPostal address from schema.org data on the website
contactsarrayAI-extracted structured contacts from the website (name, position, email, phone). Add-on: contact extractor
job_listingsarrayAI-extracted job listings from the careers page. Add-on: job extractor
gbp_completenessobjectscore (0-100), sub_scores, missing_fields, lead_priority_flag. Add-on: GBP completeness score
phone_validationobjectvalid, number_type, country_iso, carrier, e164, international, national. Add-on: phone validator
facebookobjectPage name, followers, likes, category, contact info, runs_ads. Add-on: Facebook Page Scraper
linkedin_companyobjectFollower count, employee count, industry, headquarters, founded, specialties, up to 5 employees[] (decision-makers with title and profile URL), up to 5 recent_posts[] (text + engagement counts), resolved_from_numeric_id. Add-on: LinkedIn Company Scraper
twitterobjectFollowers, tweet count, media count, bio, location, verified, blue-verified. Add-on: Twitter/X profile scraper.
instagramobjectFollowers, following, posts count, biography, business category, up to 6 recent_posts[] with caption + engagement. Add-on: Instagram scraper
tiktokobjectFollowers, hearts, video count, signature, up to 6 recent_videos[] with views / likes / duration / play URL. Add-on: TikTok profile scraper
youtubeobjectHandle, subscribers, video count, up to 6 latest_videos[] with view count + thumbnail. Add-on: YouTube channel scraper
sslobjectCertificate issuer, validity dates, days_until_expiry, flag. Add-on: SSL inspector
whoisobjectRegistrar, domain_age, MX provider, has_spf, has_dmarc, has_dkim. Add-on: WHOIS + DNS lookup
seo_auditobjectTitle, meta description (with lengths), canonical URL, robots, HTML lang, Open Graph tags. Add-on: SEO metadata extractor
tech_stackobjecttechnologies[] — array of objects with {name, categories[], confidence, version?}. category_summary — map grouped by underscored category name (cdn, analytics, web_servers, cms, tag_managers, programming_languages, etc). Add-on: tech stack detector
broken_linksobjectbroken_count, broken_links[], site_health_flag, plus possibly_protected_count / possibly_protected_links for Cloudflare/WAF 403 false positives. Add-on: broken link checker
review_intel_mathobjectZero-fetch review-intelligence: rating_trend, rating_trend_delta, review_velocity, owner_response_rate, months_since_last_review, freshness_flag, reviews_analyzed. Baked into gmaps when includeReviews is on
review_intel_llmobjectLLM analysis of user_reviews: overall_sentiment, sentiment_score, complaint_themes[], praise_themes[], owner_tone, red_flags[], opportunity_signals[], llm_provider, llm_model. Add-on: Review Intelligence
local_rankobjectPosition of this place within its (category, city) cohort: position_in_category, cohort_size, review_share_pct, top3_gap, rating_gap, category_key. Baked into gmaps when the cohort has 2+ members
chainobjectChain / franchise detection: is_chain, chain_name, chain_confidence, chain_category. Add-on: Chain Detection
reservation_platformsstring[]Deduped list of reservation platform names parsed from reservations[] (opentable, resy, thefork, quandoo, tock, sevenrooms, and more). Baked into gmaps
duplicate_ofstringSet to the canonical place's place_id when this row is a probable duplicate of an earlier place in the same run. Empty on unique / canonical rows
dedupe_confidencenumber1.0 for exact-title + exact-address matches; 0.7 for exact-title + normalized-phone matches. Only present when duplicate_of is set
complianceobjectLegal + tracking hygiene per website: site_language, has_impressum, impressum_url, has_privacy_policy, privacy_policy_url, cmp_detected, cookie_consent_status, consent_tool, sets_tracking_cookies, risk_flag, risk_reasons[]. Add-on: Compliance Scanner
freshnessobjectWebsite freshness digest: sitemap_lastmod, homepage_last_modified, footer_copyright_year, latest_feed_pubdate, most_recent_signal, most_recent_source, stale_flag (active / stale / abandoned / unknown), months_since_last_signal, signals_found. Add-on: Website Freshness Detector
job_boardsobjectAggregate hiring signals per company. Currently emits linkedin_jobs with open_roles_count, senior_roles_count, avg_applicants, sample_titles[], sample_locations[], sample_seniorities[], sample_remote_options[], top_industry, hiring_signal (hot / active / quiet / no_roles / unknown), match_strategy. Add-on: Job Board Presence
page_speed_auditobjectFlat shape (not nested under scores): performance_score, accessibility_score, best_practices_score, seo_score (0-100), Core Web Vitals lcp_ms / cls / fcp_ms / tbt_ms / si_ms / ttfb_ms / fid_ms / tti_ms, performance_flag and accessibility_flag (critical / poor / average / good), psi_status, strategy, final_url, and accessibility_issues[] (up to 20 failing Lighthouse audits with id, title, impact, count). Add-on: PageSpeed + Accessibility audit
icebreakerobjectAI-generated personalized cold-outreach opening line referencing whichever enrichment signals landed. Consumes every other add-on's output. Add-on: AI icebreaker generator
_enrichment_statusobjectPer-add-on status: success, no_data, failed, timed_out, or skipped. Present when any add-on is enabled and waitForAddOns is true

About popular_times

Set includePopularTimes: true to enrich each result with Google's visitor-traffic histogram. The output shape is Day → Hour → Popularity(0-100) with English day keys (Sunday, Monday, ...) regardless of the input language, so the data stays portable across runs.

Each enrichment is one extra request to Google's place-detail endpoint, run sequentially (concurrency 1) with a 300 ms delay. Budget roughly 1.5 s per place on top of the SERP scrape. Only places with enough foot traffic for Google to compute the histogram will have non-null data. Smaller venues keep popular_times: null, which is expected, not an error.

Example:

"popular_times": {
"Sunday": {"12": 41, "13": 56, "14": 63, "15": 70, "16": 73, "17": 83, "18": 76},
"Monday": {"11": 43, "12": 61, "13": 60, "14": 53}
}

About user_reviews

Set includeReviews: true to enrich each result with the place's featured reviews. Each review has the author's name, star rating, publication date, profile link, and text when the reviewer wrote one. Adds one extra request per place (about 300 ms).

  • maxReviewsPerPlace caps the number of reviews per place (default 25). Set to 0 for no cap. The actual number returned depends on how many featured reviews Google surfaces, typically up to 8 for high-traffic places.
  • reviewsSort controls ordering: most_relevant (default), newest, highest_rating, lowest_rating.
  • reviewsNewerThan (YYYY-MM-DD) filters to reviews published on or after that date. It automatically switches sort to newest so scraping stops as soon as older reviews are reached.

Reviews come from Google's place-preview endpoint, which returns the same featured reviews shown in the Google Maps overview panel. These are the reviews Google has selected as most helpful, typically the most recent positive reviews with photos or detailed text. Only places with enough review activity will return non-empty user_reviews; low-traffic places get user_reviews: [].

"user_reviews": [
{
"name": "Jane Doe",
"profile_link": "https://www.google.com/maps/contrib/...",
"rating": 5,
"text": "Excellent cuisine, warm atmosphere.",
"published_at": "2025-12-10",
"response_from_owner": null
}
]

Enrichment add-ons

After the Maps scrape finishes, this actor can trigger enrichment sub-actors on every website, phone, and place it found and merge everything back into a single dataset. One row per place, with contacts, jobs, emails, phones, socials, GBP completeness score, SSL status, WHOIS, tech stack, broken links, Facebook / LinkedIn / Twitter / Instagram / TikTok data, verified emails, and even a personalized cold-outreach opening line attached.

Single-dataset mode (waitForAddOns: true, the default) is designed for n8n, Zapier, and Make workflows where you don't want to join multiple datasets. The scraper waits for each enabled add-on to finish, merges the results by CID, domain, phone, or place_id, and pushes the enriched row once everything is in. Every row also gets an _enrichment_status object showing what happened per add-on: success, no_data, failed, timed_out, or skipped.

Multi-dataset mode (waitForAddOns: false) fires the add-ons and exits. You'll find their run IDs in the key-value store under keys like CONTACT_EXTRACTOR_RUN_ID, JOB_EXTRACTOR_RUN_ID, EMAIL_SCRAPER_RUN_ID, EMAIL_VERIFIER_RUN_ID, FACEBOOK_SCRAPER_RUN_ID, LINKEDIN_COMPANY_SCRAPER_RUN_ID, TWITTER_SCRAPER_RUN_ID, INSTAGRAM_SCRAPER_RUN_ID, TIKTOK_SCRAPER_RUN_ID, SSL_INSPECTOR_RUN_ID, and so on. Consume them however you want.

Add-ons are grouped into five packs in the input UI: Contact Enrichment, Social Media Enrichment, Website Health Enrichment, Data Quality (zero-fetch), and AI-Powered Enrichment. Every sub-actor add-on is opt-in, billed by its own standalone actor, and skipped cleanly with no billing event on places that lack the required input (no website, no phone, no discovered email, and so on). Data Quality signals are baked into gmaps itself: no sub-actor calls, no billing events beyond the base $1/1000 rate.

Data Quality (zero-fetch, baked into gmaps)

These signals cost nothing extra and never fire a sub-actor. They read from data the base scrape already returned.

Review Intelligence math signals

Fires automatically when includeReviews: true populates user_reviews[]. No toggle. Adds review_intel_math to each row with:

  • rating_trend: improving / stable / declining / insufficient_data. Compares avg rating of last 6 months vs prior 6 months (needs at least 3 reviews per pool).
  • rating_trend_delta: numeric delta in the trend calculation.
  • review_velocity: reviews per month, trailing 6 months.
  • owner_response_rate: 0.0 to 1.0. Fraction of reviews with a non-empty owner response.
  • months_since_last_review: freshness input for the flag below.
  • freshness_flag: active (up to 3 months), quiet (3 to 12), stale (more than a year), no_data.
  • reviews_analyzed: count.

Great for "your rating is dropping" outreach and "no review since 2023, needs a reputation-management pitch" filters. LLM-based sentiment analysis, complaint themes, and owner-tone classification live in the separate Review Intelligence Analyzer sub-actor (requires an LLM key).

Local Rank & Market Share

Fires automatically when two or more places in the current run share the same <category> | <city> cohort. Adds local_rank to every eligible place row:

  • position_in_category: 1-based rank by review_count within the cohort.
  • cohort_size: total places in the same category+city.
  • review_share_pct: this place's review_count divided by the cohort total, times 100.
  • top3_gap: reviews needed to reach position 3 (0 when already in the top 3).
  • rating_gap: this place's rating minus the cohort average rating.
  • category_key: <category> | <city> identifier.

The core local-SEO pitch: "You're #7 for yoga studios in Brooklyn. The competitor at #3 has 4x your reviews." Zero cost, high signal.

Chain / Franchise detection

Enable with enableChainDetection: true. Curated allowlist of ~60 well-known chains and franchises (McDonald's, Starbucks, Marriott, IKEA, Rewe, dm-drogerie, MediaMarkt, Kempinski, Sparkasse, and more, plus DACH-specific entries). Adds chain to each row:

  • is_chain: true / false.
  • chain_name: canonical name from the allowlist.
  • chain_confidence: 1.0 for exact title match, 0.85 for substring match.
  • chain_category: fastfood / hotels / retail / grocery / pharmacy / telecom / banking / coffee / logistics.

Freelancers use is_chain: false as a filter — a chain branch can't buy a bespoke website from a solo shop. Opt-in because not every user wants the filter.

Duplicate detection (baseline)

Always on, no toggle, no billing event. Every row gets duplicate_of populated when a fuzzy match against an earlier row in the same run finds:

  • Exact title + normalized address match → dedupe_confidence: 1.0
  • Exact title + normalized phone match → dedupe_confidence: 0.7

Duplicates are tagged, not removed. If you want a compact output, filter rows where duplicate_of is truthy in your downstream workflow. Every list builder needs this — we bake it in for free.

Reservation platforms

Always on, no toggle. Parses the reservations[] array's source field into a canonical platform list on reservation_platforms. Detects OpenTable, Resy, TheFork, Quandoo, Tock, SevenRooms, Bookatable, Yelp, Eveve, ReservationSuite, and Reserve with Google. Unknown platforms are preserved with an other: prefix.

F&B tool vendors use this to filter for booking-tool prospects ("uses TheFork, could switch to OpenTable") or greenfield leads ("no reservation platform at all — direct book potential").

Contact Enrichment

GBP Completeness score

Enable with enableGbpCompleteness: true. Zero-fetch. Powered by the GBP Completeness Audit.

For every place, scores the Google Business Profile on up to 8 signals (has_website, has_phone, has_hours, has_description, has_category, photo_count, review_count, is_claimed) and adds gbp_completeness to each row. The audit only scores signals it can OBSERVE in the input record — denominator reports how many of the 8 had a source field. On the SERP-only path this is typically 4 (has_category, has_phone, has_website, review_count). Downstream users filtering by low score should also gate on denominator >= N for confidence.

{
"score": 62,
"denominator": 8,
"signals_observed": ["has_website", "has_phone", "has_hours", "has_description", "has_category", "photo_count", "review_count", "is_claimed"],
"sub_scores": {"has_website": 1, "has_phone": 1, "has_hours": 0, "has_description": 0, "has_category": 1, "photo_count": 0.5, "review_count": 1, "is_claimed": 1},
"missing_fields": ["hours", "description"],
"lead_priority_flag": "NORMAL"
}

lead_priority_flag: "HIGH" fires only when we have positive evidence of a gap — has_website observed AND empty, OR is_claimed observed AND false. Never on unobserved signals. null when zero signals were observable. score is also null when nothing was observable.

Phone number validation and enrichment

Enable with enablePhoneValidation: true. Powered by the Phone Number Validator.

For every place with a phone number, validates the number and enriches with country, line type (mobile / fixed / VoIP), carrier (when detectable), and E.164 / international / national formatted variants. Adds phone_validation to each row. Skipped cleanly for places without a phone.

Enable with enableEmailExtraction: true. Pure regex plus a Cloudflare Email Protection decoder that unwraps data-cfemail obfuscation used by many European DSGVO-conscious sites. No LLM key needed, no AI cost. Powered by the Website Email & Phone Scraper.

Populates these fields on each place row:

  • emails_from_website: email addresses from mailto: links, plain text, structured data, and Cloudflare-obfuscated markup
  • phones_from_website: phone numbers from tel: links and international-format text
  • social_urls: linkedin, facebook, instagram, twitter, youtube
  • address_from_website: postal address from schema.org JSON-LD when available

This add-on is also a prerequisite for two others: the Facebook Page Scraper (which needs the Facebook page URL) and the Email Verifier (which needs the emails). Enable it when you turn on either of those and the scraper will auto-enable it for you.

Email verification

Enable with enableEmailVerification: true. Auto-enables the Email Extractor since it needs the emails to verify. Powered by the Email Verifier.

Once the email extractor discovers addresses, every one is passed through syntax + MX lookup + disposable-domain check + role-account detection + provider inference (Google Workspace, Microsoft 365, Zoho, IONOS, and more). No SMTP RCPT probing (datacenter IPs get soft-blocked, accept-all providers give noise). In v1, verified emails land in a separate dataset which you join back by email address. Merger integration into each place row is a v2 item.

Social Media Enrichment

Every social add-on chains after the Email Extractor add-on, which is where the social profile URLs are discovered on each company website. Enable Facebook or LinkedIn and gmaps will auto-enable the Email Extractor for you.

Many businesses link to their global brand page rather than a per-location page, so a search for "Andersen Berlin" can return the Andersen Warsaw HQ's LinkedIn / Facebook / Twitter. To help you separate brand-level noise from real location-level signal, every social block carries an entity_match_confidence field (high / medium / low). It's computed at merge time by comparing the social profile's name + location vs the gmaps place's title + city. If you need per-location data, filter entity_match_confidence == "high".

Facebook Page Scraper

Enable with enableFacebookScrape: true. Auto-enables the Email Extractor. Powered by the Facebook Page Scraper.

For every place whose website links to a Facebook Page, scrapes the FB Page for followers, likes, category, contact info, rating, and whether the page is currently running ads. The runs_ads field is a strong buying-signal indicator: businesses actively spending on Facebook Ads are almost always open to related pitches (SEO, marketing automation, CRM). Adds facebook to each row.

Higher per-result cost than the other add-ons because the scraper uses a residential proxy to avoid Facebook's login wall on datacenter IPs.

LinkedIn Company Page Scraper

Enable with enableLinkedInCompanyScrape: true. Auto-enables the Email Extractor. Powered by the LinkedIn Company Scraper.

For every place whose website links to a LinkedIn Company page (social_urls.linkedin), scrapes the page for follower count, employee count and headcount range, industry, headquarters, founded year, company type, description, specialties, up to 5 decision-maker employees (CEO / CTO / CMO / CFO / COO / Founder / VP / Head of / Director), and the 5 most recent public posts with engagement counts. Adds linkedin_company to each row. Great for B2B outreach: firmographic signals plus real people plus recent content give you a personalized angle for every lead.

Handles legacy numeric-ID URLs. Older sites sometimes embed /company/19051-style URLs that LinkedIn walls off from guest access. The sub-actor resolves those to a slug (/company/kempinski-hotels) by probing candidates derived from the parent website domain. resolved_from_numeric_id: true on the row when this happens.

Only Company pages qualify. Personal profiles (/in/), schools, and showcase pages are skipped cleanly.

Twitter / X Profile Scraper

Enable with enableTwitterScrape: true. Auto-enables the Email Extractor. Powered by the Twitter / X Profiles + Tweets Scraper.

For every place whose website links to a Twitter or X profile, scrapes follower count, following count, tweet count, media count, bio, location, verified state, blue-verified state, join date, plus up to 5 recent tweets with engagement (replies, retweets, likes, quotes, views). Adds twitter to each row. Uses a residential proxy since X rate-limits datacenter IPs aggressively.

Instagram Profile Scraper

Enable with enableInstagramScrape: true. Auto-enables the Email Extractor. Powered by the Instagram Scraper.

For every place whose website links to an Instagram profile, scrapes followers, following, posts count, biography, external URL, verified state, business category, whether the account is flagged as business, and up to 6 recent posts with caption, media type, permalink, timestamp, and engagement. Adds instagram to each row. Only profile URLs qualify: content paths (/p/, /reel/, /tv/) are skipped cleanly.

TikTok Profile Scraper

Enable with enableTikTokScrape: true. Auto-enables the Email Extractor. Powered by the TikTok Profile Scraper.

For every place whose website links to a TikTok profile (@handle), scrapes follower count, following count, heart count, video count, signature, verified state, and up to 6 most recent videos (id, caption, cover, play URL, duration, views, likes, comments, shares). Adds tiktok to each row.

YouTube Channel Scraper

Enable with enableYouTubeChannelScrape: true. Auto-enables the Email Extractor. Powered by the YouTube Channel Scraper.

For every place whose website links to a YouTube channel, scrapes the channel handle, name, subscriber count, video count, and the 6 most recent uploads with title, view count, thumbnail, and publish date. Adds youtube to each row. Legacy /user/ and /c/ URLs are auto-normalised to the /@handle form before scraping.

Website Health Enrichment

Zero-touch signals about each website. No LLM key needed, no AI cost. Handy for cold outreach ("your SSL expires in 12 days"), competitive research, and filtering leads by tech stack.

SSL certificate inspection

Enable with enableSslInspection: true. Powered by the SSL Certificate Inspector.

Inspects the SSL/TLS certificate on each company website. Adds ssl with issuer, validity dates, days-until-expiry, and a flag field (ok, expiring_soon, expired, self_signed, invalid). Businesses with expired certs or self-signed setups are often behind on their tech.

WHOIS and DNS lookup

Enable with enableWhoisDns: true. Powered by the Domain WHOIS & DNS Lookup.

Looks up WHOIS (registrar, created date, domain age) and DNS (A / MX / TXT / NS records, has_spf, has_dmarc, has_dkim, email_provider, mx_provider) for each domain. Adds whois to each row. Domain age is a strong lead-quality signal, and mx_provider (Google Workspace, Microsoft 365, IONOS, none) is a targeting signal for IT services.

SEO metadata extraction

Enable with enableSeoAudit: true. Powered by the SEO Metadata Extractor.

Extracts SEO signals from every homepage: title (with length), meta description (with length), canonical URL, robots directive, HTML lang, and Open Graph title/description. Adds seo_audit to each row.

Website tech stack detection

Enable with enableTechStack: true. Powered by the Website Tech Stack Detector.

Detects CMS, frameworks, analytics, servers, CDN, and other technologies via a Wappalyzer-style pattern set. Adds tech_stack to each row with a technologies list and a category_summary grouped by CDN / analytics / web-server / etc.

Enable with enableBrokenLinkCheck: true. Powered by the Broken Link Checker.

Crawls the homepage plus one hop of internal links (up to 15 URLs, no external links, no full-site crawl) and flags any broken links (4xx / 5xx / timeouts / DNS / SSL). Adds broken_links with a broken_count, site_health_flag (healthy, minor_issues, many_broken), and the actual broken URL list.

Also separates out Cloudflare / WAF false positives. Any URL that returned a 403 from bot-detection edges (Cloudflare, DataDome, Imperva markers in the body / headers) is surfaced under possibly_protected_count / possibly_protected_links and does NOT count toward broken_count or degrade site_health_flag. Prevents the "our site looks healthy in Chrome but the checker reports 5 broken" false-positive class.

PageSpeed + Accessibility audit

Enable with enablePageSpeedAudit: true and provide googleApiKey. Powered by the PageSpeed + Accessibility Audit.

Runs Google's PageSpeed Insights API v5 against each place's homepage and returns Lighthouse scores (Performance, Accessibility, Best Practices, SEO on a 0-100 scale) plus Core Web Vitals (LCP, CLS, FCP, TBT, SI, TTFB, FID, TTI). Adds page_speed_audit to each row. Bring your own Google API key (free tier is 25,000 queries/day). Set pageSpeedStrategy to mobile (default) or desktop.

Score and Core Web Vital fields are flattened onto the block root — read performance_score, accessibility_score, best_practices_score, seo_score directly, not nested under a scores object. Each also gets a paired _flag (critical / poor / average / good) derived from Lighthouse's own thresholds so you can filter without maintaining threshold tables downstream.

The block also ships accessibility_issues[] — up to 20 failing Lighthouse Accessibility audits, each with id, title, impact (critical / serious / moderate / minor), and count of nodes affected. Straight into an outreach pitch: "your site fails WCAG on 12 checks, top offender is missing form labels on 8 nodes." Filter for impact == "critical" to surface only lawsuit-adjacent issues (ADA / EAA / BFSG-actionable).

Other useful root-level fields: strategy (mobile / desktop), final_url (URL after redirects), and psi_status (success / error / rate_limited — safer than checking whether scores are null).

How to get your Google API key (3 minutes, free tier: 25,000 queries/day):

  1. Sign in at console.cloud.google.com, pick or create a project (skip the billing prompt — PSI is free).
  2. Open the API Library, search PageSpeed Insights API, click Enable.
  3. Open Credentials, click Create Credentials → API key, copy the value.
  4. Recommended: click the new key → Edit API key → Restrict key, check only PageSpeed Insights API under API restrictions. Makes a leaked key useless for paid Google APIs.
  5. Paste the key into googleApiKey.

This is not an LLM key. It's a Google Cloud API key specifically for PageSpeed Insights.

Enable with enableComplianceScan: true. Powered by the Compliance Scanner.

Fetches each place's homepage and detects legal-page presence and consent-management signals. Adds compliance to each row with:

  • site_language — detected page language.
  • has_impressum, impressum_url — German-law imprint page (also matches English imprint, legal notice). Nullable: null when probe couldn't determine (timeout / DNS / 5xx), never false on probe failure.
  • has_privacy_policy, privacy_policy_url — privacy / Datenschutz page. URL is fetch-and-verified against privacy-page vocabulary in the target's title/H1. Also nullable on probe failure.
  • cmp_detected — a Consent Management Platform (Usercentrics, Cookiebot, Borlabs, OneTrust, CookieYes, Klaro, Iubenda, consentmanager) was found in the HTML source.
  • cookie_consent_statusdetected (CMP found) / not_detected (tracking scripts present without a CMP wrapper) / inconclusive (no clear evidence either way — safe default).
  • sets_tracking_cookies — HTTP-level cookies set on load.
  • risk_flagLOW / MEDIUM / HIGH / UNKNOWN. HIGH fires on missing Impressum on a German-language site (Abmahnung-actionable). UNKNOWN on probe failure.
  • risk_reasons[] — e.g. missing_impressum_de_site, missing_impressum, missing_privacy_policy, tracking_without_consent, probe_failed. tracking_without_consent fires only when cookie_consent_status == "not_detected" AND tracking is evidenced — never on inconclusive.

Great lead-quality filter — DACH sites with risk_reasons: ["missing_impressum"] are near-guaranteed to be either shady or a hobby project. Pairs well with sales agencies pitching legal-content packages.

Not currently extracted: Handelsregister / USt-ID / Steuernummer numbers. Planned for a follow-up release.

Website Freshness Detection

Enable with enableFreshnessCheck: true. Powered by the Website Freshness Detector.

Probes each place's website for up to four independent signals of when it was last touched, then rolls them into a single stale_flag verdict. Adds freshness to each row with:

  • sitemap_lastmod — MAX <lastmod> across the site's sitemap.xml URL entries.
  • homepage_last_modifiedLast-Modified HTTP header on the homepage.
  • footer_copyright_year — highest 4-digit year found near a copyright anchor in the footer (weakest signal — footer years are frequently forgotten).
  • latest_feed_pubdate — MAX <pubDate> / <updated> across the site's RSS/Atom feeds.
  • most_recent_signal — the winning date across the four signals (sitemap > homepage_header > feed > footer_copyright_year in precedence).
  • most_recent_source — which signal source produced most_recent_signal (sitemap / homepage_header / feed / footer).
  • months_since_last_signal — age of most_recent_signal in months.
  • signals_found — 0 to 4.
  • signal_note — populated when only footer_copyright_year was available, explaining why the verdict is unknown rather than trusting a raw footer year.
  • stale_flag: active (< 6 months) / stale (6–18 months) / abandoned (> 18 months) / unknown (no signal, or only footer year available).

Killer web-design agency pitch angle: filter for freshness.stale_flag == "stale" or "abandoned" in your ICP and pitch redesigns. The concrete "your sitemap's last lastmod is 2019-03, your RSS feed died in 2020" opener lands harder than a generic "your site looks old" cold email. Also a signal that the business may be struggling — pair with broken_links.site_health_flag for a stronger read.

Review Intelligence (LLM analysis)

Enable with enableReviewIntelligence: true. Powered by the Review Intelligence Analyzer.

Runs your fetched Google reviews through an LLM for structured qualitative analysis. Auto-enables includeReviews. Adds review_intel_llm to each row with:

  • overall_sentimentstrongly_positive / positive / mixed / negative / strongly_negative.
  • sentiment_score — numeric −1.0 to 1.0.
  • complaint_themes[] — up to 5 recurring complaint themes, each {theme, frequency, sample_quote} with a verbatim snippet from the reviews.
  • praise_themes[] — up to 5 recurring praise themes, same shape.
  • owner_toneprofessional / defensive / dismissive / absent / mixed, based on how the owner replies (or doesn't).
  • red_flags[] — controlled vocab: fake_review_pattern, recent_1_star_cluster, no_owner_response, hostile_owner_response, rating_drop_recent, language_barrier.
  • opportunity_signals[] — controlled vocab: ready_for_promotion, high_repeat_customer, asks_for_specific_service, seasonal_demand, underrated_gem.

Filter for red_flags containing no_owner_response or recent_1_star_cluster to find prime reputation-management leads, or fold complaint_themes[0].theme straight into your cold-email opener.

Requires your own LLM API key (see below). Different from the built-in zero-fetch review_intel_math (regex + histogram math, no LLM) — this one produces qualitative insights. Run both for a full picture.

Job Board Presence (LinkedIn Jobs)

Enable with enableJobBoardPresence: true. Reuses the existing LinkedIn Jobs Scraper — no new sub-actor.

For every place, looks up open roles on LinkedIn Jobs and returns aggregate hiring signals. Prefers the LinkedIn company URL discovered by the LinkedIn Company Scraper add-on for an exact f_C=<company_id> filter (accurate). Falls back to a name-based search when no LinkedIn URL is available (imperfect — name collisions are possible; match_strategy: "company_name" on the row lets you filter these out downstream).

Adds job_boards.linkedin_jobs to each row with open_roles_count, senior_roles_count, avg_applicants, top_industry, up to 10 sample_titles, up to 10 sample_locations, top 3 seniority/remote buckets, and a hiring_signal verdict: hot (3+ senior OR 20+ total open roles) / active (3+) / quiet (1-2) / no_roles / unknown.

Killer B2B outreach signal: staffing agencies pitching to companies actively hiring, SaaS tools tuned to seniority mix, competitive intelligence on hiring velocity. Billed at $0.001 per job result by the LinkedIn Jobs Scraper — capped at 25 rows per company by default.

AI-Powered Enrichment

All three AI-powered add-ons require your own LLM API key (see below). They all respect the outputLanguage input so extracted text is written in the language you choose.

AI contact extraction

Enable with enableContactExtraction: true. Powered by the Website Contact Extractor.

Populates contacts on each place row with structured records (name, firstname, lastname, salutation, academic_title, position, department, email, phone, linkedin_url, source_url) extracted from team, about, impressum, and contact pages.

AI job listing extraction

Enable with enableJobExtraction: true. Powered by the Website Job Extractor.

Populates job_listings on each place row with open positions (title, location, department, job_url, application_url, posted_at, ats_system) extracted from careers pages and ATS portals: Personio, Greenhouse, Ashby, Workable, and others.

AI icebreaker generator

Enable with enableAiIcebreaker: true. Powered by the AI Icebreaker Generator.

For every place, generates ONE personalized cold-outreach opening line referencing the business's actual details. Adds icebreaker to each row with the line, the LLM provider used, and the model.

Consumes every other enrichment signal. The icebreaker fires LAST, after every other add-on you enabled has settled into the place record. That means the LLM prompt sees the fully-merged place: LinkedIn scale and recent posts, Facebook / Twitter / Instagram / TikTok / YouTube recent content with engagement counts, decision-maker contacts, open jobs, tech stack, SEO meta, SSL expiry warnings, WHOIS age, PageSpeed scores, GBP gaps, broken-link health, phone validation. The model picks the sharpest angle for the ICP: hiring signals for tech agencies, recent posts for retail, GBP gaps for hospitality, LinkedIn tenure for professional services, SSL expiry for time-urgency plays. Enable more add-ons for richer lines.

Two modes:

  • Base (zero-fetch, default): uses only fields already on the place plus whatever enrichment landed. Cheap, fast.
  • Enriched (enableIcebreakerEnriched: true): also fetches each place's homepage and passes a condensed summary alongside the enrichment context. Higher quality lines, higher cost.

Two more knobs to steer the output:

  • Tone (icebreakerTone): friendly (default), professional, casual, or direct.
  • Focus (icebreakerFocus): optional free-text guidance. Example: "Focus on their photo count and their newest reviews", or "Mention that we specialize in local SEO for care homes". The text is passed to the LLM alongside the place record so every line reflects your outreach angle.

Language is controlled by outputLanguage (same knob used by contact and job extraction).

Browser fallback for JavaScript-rendered sites

Some company websites are built with React, Vue, or Angular and need a full browser to render. When enableBrowserFallback: true, the AI-powered add-ons re-scrape these sites with a real browser. Catches about 25% more sites but increases cost and runtime.

LLM API keys: bring your own

The three AI-powered add-ons (Contacts, Jobs, Icebreaker) need an LLM API key. You provide your own. The key is forwarded through the scraper to the AI sub-actor, and all LLM calls happen against your account. The Maps scraper itself never uses your key for anything else.

Providers (any one works; multiple enables automatic fallback):

  1. Gemini (recommended). Best quality-to-cost ratio, generous free tier. Get a key at Google AI Studio.
  2. Groq. Ultra-fast inference, generous free tier. Get a key at Groq Console.
  3. OpenRouter. Access to 100+ models. Get a key at OpenRouter.

If you're using OpenRouter and hit a "model not found" error, your key probably doesn't have access to the default paid Gemini model. Set llmModel to a free option like meta-llama/llama-3.1-8b-instruct, or browse available models at openrouter.ai/models.

Use cases

  • Lead generation. Search for businesses by type and location. Get phone numbers, websites, and emails in one run.
  • Market research. Map competitors in a geographic area with ratings, review counts, and price ranges.
  • Company enrichment. Add addresses, phone numbers, GPS coordinates, and opening hours to your existing database.
  • Local SEO monitoring. Track your business listing and competitor rankings across locations.
  • Real estate analysis. Find nearby amenities, restaurants, and services for property listings.
  • Hiring intelligence. Discover companies in a region and extract their open positions in a single pipeline.
  • Review analysis. Collect review ratings and sample reviews across competitors for sentiment analysis.

Skip already-scraped places

Use the excludeCids parameter to skip places you already have. The CID (Customer ID) is a stable Google Maps identifier that doesn't change even if the business changes its name or address.

{
"searchStrings": ["cafes in Zurich"],
"maxResultsPerQuery": 100,
"excludeCids": ["12345678901234567", "98765432109876543"]
}

This is useful for incremental scraping workflows where you only want new results.

Supported languages

Pass any ISO 639-1 language code: en, de, fr, ja, es, pt, it, ko, zh-CN, ar, nl, pl, sv, da, fi, no, cs, hu, ro, el, tr, th, vi, id, and more.

Tips for better results

  • Include the location in your search. "plumber Berlin" works better than just "plumber".
  • Use the local language for better results in non-English countries. "Zahnarzt Zurich" beats "dentist Zurich".
  • Set language to match your target market: de for German results, fr for French, and so on.
  • Use excludeCids to avoid re-scraping places you already have from previous runs.

Pricing

Pay-per-result via Apify's Pay-Per-Event billing. Only two billable events:

Place scraped — all 30+ fields per place included (photos, hours, address parsing, categories, popular_times when enabled, etc.)

Apify planPer 1,000 placesPer place
Free / Bronze$1.00$0.001
Silver$0.89$0.00089
Business (Gold)$0.75 (25% off)$0.00075
Platinum / Diamond$0.75$0.00075

Review extracted — opt-in via includeReviews: true

Apify planPer 1,000 reviewsPer review
All plans$0.30$0.0003

You are charged only for reviews you asked for (bounded by maxReviewsPerPlace, default 8, max 200). When includeReviews: false (default), zero review charges.

Actor start: $0.00005 per run (negligible, one event per GB of memory, min 1).

Example run costs (Free plan)

ScenarioPlacesReviews per placeReviews costPlaces costTotal
Just business info (no reviews)1000$0.00$0.10$0.10
Standard lead list1,0000$0.00$1.00$1.00
Lead list + first-page reviews1,0008$2.40$1.00$3.40
Reputation audit1,00050$15.00$1.00$16.00
Deep review corpus (hospitality ICP)1,000100$30.00$1.00$31.00

Business (Gold) plan gets 25% off places, same review price → 1,000 places + 50 reviews each = $15.75 instead of $16.00.

Scraping speed

Rough per-place wall-clock budget. Real numbers depend on your Apify plan's concurrency and the target's response time — these are the medians we observe on a fresh run.

ConfigTime per placeNotes
Base — just places, no reviews, no add-ons~0.3 sGoogle Maps SERP fetch + parse. Concurrency 10 default.
Places + reviews (8/place)~0.8 sSingle-request preview endpoint.
Places + reviews (50/place)~2.5 sPaginated via Google's internal BOQ endpoint. Adds one round-trip per page of 20.
Places + reviews (100/place)~4.0 sApproaching Google's ceiling (~100/place practical max).
Places + includePopularTimes: true+0.2 sOne extra request per place, concurrency-limited.
Places + one L1 website add-on (SEO, tech, WHOIS, SSL, broken links, freshness, compliance)+2-4 sIndependent sub-actor call per place. Full stack of these still runs a single shared homepage fetch per place (2026-07-23 orchestrator update).
Places + enableAiIcebreaker+3-5 sLLM roundtrip; enriched mode adds one homepage fetch (shared with the L1 add-ons above).
Full stack — every add-on enabled~8-15 sDominated by longest add-on (usually PageSpeed if enabled). Post-merge orchestration waits for icebreaker + job_boards.

For a 15-place run:

  • Base: ~5 seconds
    • all reviews (100 each): ~1 minute
    • full add-on stack: ~3-4 minutes

Set addOnWaitCapMs (default 30 min) if you want a hard ceiling — the actor stamps _enrichment_status.<add-on>: timed_out on anything that doesn't land.

Add-onPricing
GBP Completeness scoreBilled separately by the GBP Completeness Audit. Near-zero cost per place.
Phone number validationBilled separately by the Phone Number Validator
Emails, phones, socials (regex, no LLM)Billed separately by the Website Email & Phone Scraper
Email verificationBilled separately by the Email Verifier. Auto-enables the email extractor.
Facebook Page ScraperBilled separately by the Facebook Page Scraper. Higher per-result cost (residential proxy). Auto-enables the email extractor.
LinkedIn Company Page ScraperBilled separately by the LinkedIn Company Scraper. Auto-enables the email extractor.
Twitter / X Profile ScraperBilled separately by the Twitter / X Profiles + Tweets Scraper. Residential proxy required. Auto-enables the email extractor.
Instagram Profile ScraperBilled separately by the Instagram Scraper. Auto-enables the email extractor.
TikTok Profile ScraperBilled separately by the TikTok Profile Scraper. Auto-enables the email extractor.
YouTube Channel ScraperBilled separately by the YouTube Channel Scraper. Auto-enables the email extractor.
SSL certificate inspectionBilled separately by the SSL Certificate Inspector
WHOIS and DNS lookupBilled separately by the Domain WHOIS & DNS Lookup
SEO metadata extractionBilled separately by the SEO Metadata Extractor
Website tech stack detectionBilled separately by the Website Tech Stack Detector
Broken link checkBilled separately by the Broken Link Checker
PageSpeed + Accessibility auditBilled separately by the PageSpeed + Accessibility Audit. Requires your own Google API key (free tier: 25,000 queries/day).
Job Board PresenceBilled separately by the LinkedIn Jobs Scraper at $0.001 per job result. Capped at 25 rows per company by default (max ~$0.025 per company).
Compliance ScanBilled separately by the Compliance Scanner. One homepage fetch per place.
Website FreshnessBilled separately by the Website Freshness Detector. One homepage fetch per place.
Review Intelligence (LLM)Billed separately by the Review Intelligence Analyzer, plus your own LLM API cost. Auto-enables review fetch.
AI-powered contact extractionBilled separately by the Website Contact Extractor, plus your own LLM API cost
AI-powered job listing extractionBilled separately by the Website Job Extractor, plus your own LLM API cost
AI icebreaker generationBilled separately by the AI Icebreaker Generator, plus your own LLM API cost

Limitations

  • About 120 results per query. Google Maps returns a maximum of roughly 120 places per search query. For broader coverage, split your search into multiple queries by neighborhood or sub-region.
  • No individual place detail pages. Data comes from search results, not from visiting each place page individually. Most fields are populated, but some niche fields (full review text, all photos) may be limited to what Google includes in search results.
  • Search relevance. Results depend on Google's ranking algorithm. The same query may return slightly different results over time.
  • Rate limiting. Very high concurrency or very low request delays may trigger temporary blocks from Google. The defaults (10 concurrency, 300 ms delay) are tuned for reliability.

Find businesses to enrich

Real estate

Vehicles and travel

Contact enrichment

Social media enrichment

Website health enrichment

AI-powered enrichment

Support

Found a bug or have a feature request? Open an issue.