Google Maps Scraper | $1/1k Places
Pricing
from $0.75 / 1,000 place scrapeds
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
Maintained by CommunityActor stats
9
Bookmarked
674
Total users
249
Monthly active users
8 hours
Issues response
18 hours ago
Last modified
Categories
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-scraperto 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
Simple search
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"}
Add emails, phones, and social links (no API key)
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
| Parameter | Type | Default | Description |
|---|---|---|---|
searchStrings | string[] | [] | Simple search strings. Include the location in the string for geo-targeting. |
queries | object[] | [] | Structured queries with optional query, location, country, company_id. |
maxResultsPerQuery | integer | 20 | Results per query. Max 120 (Google Maps page limit). |
maxTotalResults | integer | (none) | Optional global cap across all queries combined. Leave empty for no cap. Useful for budget control. |
maxResults | integer | (none) | Deprecated alias for maxResultsPerQuery. Still works for backward compatibility. |
language | string | "en" | ISO 639-1 language code (en, de, fr, ja, es, pt, it, ko, zh, ar, etc.). |
includeReviewCount | boolean | true | Enrich each result with review_count via the Maps Embed API. Adds about 150 ms per place. |
includePopularTimes | boolean | false | Opt-in. Enrich each result with the visitor-traffic histogram by weekday × hour (0–100). Adds about 1.5 s per place. |
includeReviews | boolean | false | Opt-in. Fetch featured reviews for each place (author, rating, date, text). Adds about 300 ms per place. |
maxReviewsPerPlace | integer | 25 | Maximum reviews per place. Set to 0 for no cap. |
reviewsSort | string | most_relevant | Sort order: most_relevant, newest, highest_rating, lowest_rating. |
reviewsNewerThan | string | (none) | Only return reviews on or after this date (YYYY-MM-DD). Auto-switches sort to newest for an efficient cutoff. |
enableGbpCompleteness | boolean | false | Zero-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. |
enablePhoneValidation | boolean | false | For every place with a phone, validate and enrich with country, line type, carrier, and E.164 / international / national formats. |
enableEmailExtraction | boolean | false | Regex extractor for emails, phone numbers, and social URLs (LinkedIn, Facebook, Instagram, Twitter, YouTube). No LLM key required. Also decodes Cloudflare Email Protection markup. |
enableEmailVerification | boolean | false | Verify 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. |
enableFacebookScrape | boolean | false | Scrape 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. |
enableLinkedInCompanyScrape | boolean | false | Scrape 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. |
enableTwitterScrape | boolean | false | Scrape 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). |
enableInstagramScrape | boolean | false | Scrape each place's Instagram profile (followers, following, posts count, biography, business category, up to 6 recent posts). Auto-enables enableEmailExtraction. |
enableTikTokScrape | boolean | false | Scrape each place's TikTok profile (followers, hearts, video count, verified, signature, up to 6 recent videos with views / likes / comments / shares). Auto-enables enableEmailExtraction. |
tikTokMaxVideosPerProfile | integer | 6 | How 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. |
enableYouTubeChannelScrape | boolean | false | Scrape 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. |
enableSslInspection | boolean | false | Inspect the SSL/TLS certificate on each website. Returns issuer, expiry, days_until_expiry, and a flag (ok / expiring_soon / expired / self_signed / invalid). |
enableWhoisDns | boolean | false | WHOIS (registrar, domain age) + DNS (A / MX / TXT / NS, has_spf, has_dmarc, has_dkim, email_provider, mx_provider) for each domain. |
enableSeoAudit | boolean | false | SEO signals from every homepage: title, meta description (with lengths), canonical URL, robots directive, HTML lang, and Open Graph tags. |
enableTechStack | boolean | false | Detect CMS, frameworks, analytics, servers, CDN via a Wappalyzer-style pattern set. Adds a technologies list plus a category summary. |
enableBrokenLinkCheck | boolean | false | Crawl 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. |
enablePageSpeedAudit | boolean | false | For 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. |
googleApiKey | string | (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. |
pageSpeedStrategy | string | "mobile" | mobile or desktop. Passed straight to PageSpeed Insights. |
enableJobBoardPresence | boolean | false | Look 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. |
enableComplianceScan | boolean | false | Fetch 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. |
enableFreshnessCheck | boolean | false | Probe 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. |
enableReviewIntelligence | boolean | false | LLM-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. |
enableContactExtraction | boolean | false | AI-powered structured contact extraction (name, position, email, phone, LinkedIn). Requires your own LLM API key. |
enableJobExtraction | boolean | false | AI-powered job-listing extraction from careers pages. Requires your own LLM API key. |
enableAiIcebreaker | boolean | false | Generate a personalized cold-outreach opening line for every place. Requires your own LLM API key. |
enableIcebreakerEnriched | boolean | false | When AI icebreaker is on, also fetch each place's homepage for a richer prompt. Higher cost, higher quality. |
icebreakerTone | string | "friendly" | Voice of the icebreaker line: friendly, professional, casual, direct. |
icebreakerFocus | string | (none) | Optional free-text guidance for the icebreaker prompt (e.g. "focus on their photos", "mention we do local SEO for care homes"). |
enableBrowserFallback | boolean | false | Re-scrape JS-rendered sites (React, Vue, Angular) with a full browser. Catches about 25% more sites. |
outputLanguage | string | "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. |
geminiApiKey | string | (none) | Your Gemini API key. Free tier is generous, so this is the recommended starting point. |
groqApiKey | string | (none) | Your Groq API key. Ultra-fast inference with a free tier. |
openrouterApiKey | string | (none) | Your OpenRouter API key. Access to 100+ models. Optional fallback. |
llmModel | string | (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. |
waitForAddOns | boolean | true | When 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. |
addOnWaitCapMs | integer | 1800000 | Safety cap on how long to wait for add-ons before flushing remaining places with a timed_out status. Default 30 min. |
excludeCids | string[] | [] | Google CIDs to skip. Useful for excluding already-scraped places. |
maxConcurrency | integer | 10 | Parallel requests (1–20). |
requestDelay | integer | 300 | Delay between pagination requests in milliseconds (100–10,000). |
proxyConfiguration | object | Apify proxy | Proxy 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
| Field | Type | Description |
|---|---|---|
company_id | string | Your reference ID (from queries) or the search string |
place_id | string | Google 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. |
title | string | Business name |
category | string | Primary Google Maps category |
categories | string[] | All assigned categories |
address | string | Full formatted address |
complete_address | object | Parsed components: street, city, postal_code, state, country |
latitude | number | GPS latitude |
longitude | number | GPS longitude |
plus_code | string | Google Plus Code |
timezone | string | IANA timezone identifier |
phone | string | Phone number |
website | string | Website URL |
open_hours | object | Opening hours grouped by day of week |
popular_times | object | Visitor traffic patterns (opt-in via includePopularTimes) |
price_range | string | Price level ($ to $$$$) |
status | string | OPERATIONAL, CLOSED_TEMPORARILY, or CLOSED_PERMANENTLY |
review_count | integer | Total number of reviews |
review_rating | number | Average rating (1.0 to 5.0). rating is a duplicate alias — pick one, treat the other as deprecated. |
rating | number | Alias for review_rating (present for backward compatibility). Prefer review_rating in new integrations. |
reviews_per_rating | object | Breakdown of reviews by star rating (1 through 5) |
reviews_link | string | Direct URL to all reviews |
user_reviews | array | Sample reviews with name, rating, text, date, owner response |
images | array | Additional photos with titles |
reservations | array | Reservation links with source (TheFork, OpenTable, etc.) |
order_online | array | Online ordering links with source |
menu | object | Menu link and source |
owner | object | Business owner name and profile link |
description | string | Business description from Google |
link | string | Full Google Maps URL for this place |
cid | string | Google Maps CID: a stable identifier that persists across name and address changes |
data_id | string | Google internal data ID |
searchString | string | The search query that produced this result |
_rank | integer | Position in search results (1-based) |
scraped_at | string | ISO 8601 timestamp |
emails_from_website | string[] | 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_website | string[] | Phone numbers extracted from the company's website |
verified_emails | object[] | 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_urls | object | Social profiles from the website: linkedin, facebook, instagram, twitter, tiktok, youtube |
address_from_website | string | Postal address from schema.org data on the website |
contacts | array | AI-extracted structured contacts from the website (name, position, email, phone). Add-on: contact extractor |
job_listings | array | AI-extracted job listings from the careers page. Add-on: job extractor |
gbp_completeness | object | score (0-100), sub_scores, missing_fields, lead_priority_flag. Add-on: GBP completeness score |
phone_validation | object | valid, number_type, country_iso, carrier, e164, international, national. Add-on: phone validator |
facebook | object | Page name, followers, likes, category, contact info, runs_ads. Add-on: Facebook Page Scraper |
linkedin_company | object | Follower 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 |
twitter | object | Followers, tweet count, media count, bio, location, verified, blue-verified. Add-on: Twitter/X profile scraper. |
instagram | object | Followers, following, posts count, biography, business category, up to 6 recent_posts[] with caption + engagement. Add-on: Instagram scraper |
tiktok | object | Followers, hearts, video count, signature, up to 6 recent_videos[] with views / likes / duration / play URL. Add-on: TikTok profile scraper |
youtube | object | Handle, subscribers, video count, up to 6 latest_videos[] with view count + thumbnail. Add-on: YouTube channel scraper |
ssl | object | Certificate issuer, validity dates, days_until_expiry, flag. Add-on: SSL inspector |
whois | object | Registrar, domain_age, MX provider, has_spf, has_dmarc, has_dkim. Add-on: WHOIS + DNS lookup |
seo_audit | object | Title, meta description (with lengths), canonical URL, robots, HTML lang, Open Graph tags. Add-on: SEO metadata extractor |
tech_stack | object | technologies[] — 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_links | object | broken_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_math | object | Zero-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_llm | object | LLM 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_rank | object | Position 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 |
chain | object | Chain / franchise detection: is_chain, chain_name, chain_confidence, chain_category. Add-on: Chain Detection |
reservation_platforms | string[] | Deduped list of reservation platform names parsed from reservations[] (opentable, resy, thefork, quandoo, tock, sevenrooms, and more). Baked into gmaps |
duplicate_of | string | Set 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_confidence | number | 1.0 for exact-title + exact-address matches; 0.7 for exact-title + normalized-phone matches. Only present when duplicate_of is set |
compliance | object | Legal + 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 |
freshness | object | Website 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_boards | object | Aggregate 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_audit | object | Flat 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 |
icebreaker | object | AI-generated personalized cold-outreach opening line referencing whichever enrichment signals landed. Consumes every other add-on's output. Add-on: AI icebreaker generator |
_enrichment_status | object | Per-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).
maxReviewsPerPlacecaps the number of reviews per place (default 25). Set to0for no cap. The actual number returned depends on how many featured reviews Google surfaces, typically up to 8 for high-traffic places.reviewsSortcontrols 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 tonewestso 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 byreview_countwithin the cohort.cohort_size: total places in the same category+city.review_share_pct: this place'sreview_countdivided 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.
Emails, phones, and social links (no API key)
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 frommailto:links, plain text, structured data, and Cloudflare-obfuscated markupphones_from_website: phone numbers fromtel:links and international-format textsocial_urls:linkedin,facebook,instagram,twitter,youtubeaddress_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.
Broken link check
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):
- Sign in at console.cloud.google.com, pick or create a project (skip the billing prompt — PSI is free).
- Open the API Library, search PageSpeed Insights API, click Enable.
- Open Credentials, click Create Credentials → API key, copy the value.
- 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.
- Paste the key into
googleApiKey.
This is not an LLM key. It's a Google Cloud API key specifically for PageSpeed Insights.
Compliance & Legal Signals (DACH + intl)
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 Englishimprint,legal notice). Nullable:nullwhen probe couldn't determine (timeout / DNS / 5xx), neverfalseon 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_status—detected(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_flag—LOW/MEDIUM/HIGH/UNKNOWN.HIGHfires on missing Impressum on a German-language site (Abmahnung-actionable).UNKNOWNon probe failure.risk_reasons[]— e.g.missing_impressum_de_site,missing_impressum,missing_privacy_policy,tracking_without_consent,probe_failed.tracking_without_consentfires only whencookie_consent_status == "not_detected"AND tracking is evidenced — never oninconclusive.
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'ssitemap.xmlURL entries.homepage_last_modified—Last-ModifiedHTTP 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 producedmost_recent_signal(sitemap/homepage_header/feed/footer).months_since_last_signal— age ofmost_recent_signalin months.signals_found— 0 to 4.signal_note— populated when onlyfooter_copyright_yearwas available, explaining why the verdict isunknownrather 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_sentiment—strongly_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_tone—professional/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, ordirect. - 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):
- Gemini (recommended). Best quality-to-cost ratio, generous free tier. Get a key at Google AI Studio.
- Groq. Ultra-fast inference, generous free tier. Get a key at Groq Console.
- 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
languageto match your target market:defor German results,frfor French, and so on. - Use
excludeCidsto 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 plan | Per 1,000 places | Per 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 plan | Per 1,000 reviews | Per 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)
| Scenario | Places | Reviews per place | Reviews cost | Places cost | Total |
|---|---|---|---|---|---|
| Just business info (no reviews) | 100 | 0 | $0.00 | $0.10 | $0.10 |
| Standard lead list | 1,000 | 0 | $0.00 | $1.00 | $1.00 |
| Lead list + first-page reviews | 1,000 | 8 | $2.40 | $1.00 | $3.40 |
| Reputation audit | 1,000 | 50 | $15.00 | $1.00 | $16.00 |
| Deep review corpus (hospitality ICP) | 1,000 | 100 | $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.
| Config | Time per place | Notes |
|---|---|---|
| Base — just places, no reviews, no add-ons | ~0.3 s | Google Maps SERP fetch + parse. Concurrency 10 default. |
| Places + reviews (8/place) | ~0.8 s | Single-request preview endpoint. |
| Places + reviews (50/place) | ~2.5 s | Paginated via Google's internal BOQ endpoint. Adds one round-trip per page of 20. |
| Places + reviews (100/place) | ~4.0 s | Approaching Google's ceiling (~100/place practical max). |
Places + includePopularTimes: true | +0.2 s | One extra request per place, concurrency-limited. |
| Places + one L1 website add-on (SEO, tech, WHOIS, SSL, broken links, freshness, compliance) | +2-4 s | Independent 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 s | LLM roundtrip; enriched mode adds one homepage fetch (shared with the L1 add-ons above). |
| Full stack — every add-on enabled | ~8-15 s | Dominated 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-on | Pricing |
|---|---|
| GBP Completeness score | Billed separately by the GBP Completeness Audit. Near-zero cost per place. |
| Phone number validation | Billed separately by the Phone Number Validator |
| Emails, phones, socials (regex, no LLM) | Billed separately by the Website Email & Phone Scraper |
| Email verification | Billed separately by the Email Verifier. Auto-enables the email extractor. |
| Facebook Page Scraper | Billed separately by the Facebook Page Scraper. Higher per-result cost (residential proxy). Auto-enables the email extractor. |
| LinkedIn Company Page Scraper | Billed separately by the LinkedIn Company Scraper. Auto-enables the email extractor. |
| Twitter / X Profile Scraper | Billed separately by the Twitter / X Profiles + Tweets Scraper. Residential proxy required. Auto-enables the email extractor. |
| Instagram Profile Scraper | Billed separately by the Instagram Scraper. Auto-enables the email extractor. |
| TikTok Profile Scraper | Billed separately by the TikTok Profile Scraper. Auto-enables the email extractor. |
| YouTube Channel Scraper | Billed separately by the YouTube Channel Scraper. Auto-enables the email extractor. |
| SSL certificate inspection | Billed separately by the SSL Certificate Inspector |
| WHOIS and DNS lookup | Billed separately by the Domain WHOIS & DNS Lookup |
| SEO metadata extraction | Billed separately by the SEO Metadata Extractor |
| Website tech stack detection | Billed separately by the Website Tech Stack Detector |
| Broken link check | Billed separately by the Broken Link Checker |
| PageSpeed + Accessibility audit | Billed separately by the PageSpeed + Accessibility Audit. Requires your own Google API key (free tier: 25,000 queries/day). |
| Job Board Presence | Billed 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 Scan | Billed separately by the Compliance Scanner. One homepage fetch per place. |
| Website Freshness | Billed 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 extraction | Billed separately by the Website Contact Extractor, plus your own LLM API cost |
| AI-powered job listing extraction | Billed separately by the Website Job Extractor, plus your own LLM API cost |
| AI icebreaker generation | Billed 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.
Related actors
Find businesses to enrich
- Kleinanzeigen.de Scraper: Germany
- Willhaben.at Scraper: Austria
- Tutti.ch Scraper: Switzerland
- Subito.it Scraper: Italy
- Marktplaats.nl Scraper: Netherlands
Real estate
- Homegate.ch Scraper: Swiss real estate
- Immowelt.de Scraper: German real estate
- Oikotie.fi Scraper: Finnish real estate
Vehicles and travel
Contact enrichment
- Website Email & Phone Scraper
- Website Contact Extractor
- Email Verifier
- Phone Number Validator
- GBP Completeness Audit
Social media enrichment
- Facebook Page Scraper
- LinkedIn Company Scraper
- LinkedIn Jobs Scraper
- Twitter / X Profiles + Tweets Scraper
- Instagram Scraper
- TikTok Profile Scraper
- YouTube Channel Scraper
Website health enrichment
- SSL Certificate Inspector
- Domain WHOIS & DNS Lookup
- SEO Metadata Extractor
- Website Tech Stack Detector
- Broken Link Checker
- PageSpeed + Accessibility Audit
AI-powered enrichment
Support
Found a bug or have a feature request? Open an issue.