# Google Maps Email & Social Leads Scraper (`automation-lab/google-maps-business-website-email-and-social-contact-leads`) Actor

Search Google Maps by keyword and location, then scan each business website for public emails, phone links and Facebook, Instagram, LinkedIn, X, YouTube and TikTok profiles. One flat lead row per business.

- **URL**: https://apify.com/automation-lab/google-maps-business-website-email-and-social-contact-leads.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.02 / 1,000 business leads

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Maps Email & Social Leads Scraper

Turn a Google Maps search into a prospect list. Enter a business type ("dentist", "roofing contractor", "coffee shop") and one or more cities. The Actor collects the matching businesses from Google Maps and then visits each business website. From the website it reads public **emails**, **phone links** and **social profiles**: Facebook, Instagram, LinkedIn, X (Twitter), YouTube, TikTok and Pinterest.

You get **one flat row per business**: name, address, Maps phone, website, rating, emails and social links side by side. The row is ready for a CRM import, a cold-email tool or a spreadsheet.

- 🔎 Search by **keyword × location**: several keywords and several cities in one run.
- 📧 **Emails** from mailto links, page text and Cloudflare-protected addresses. Emails on the business website's own registrable domain (Public Suffix List aware, so `acme.co.uk` and `acme.github.io` are not confused with other sites on the same suffix) are listed first; the rest keep page order.
- 📱 **Social links**: Facebook, Instagram, LinkedIn, X, YouTube, TikTok, Pinterest.
- 📄 Reads the homepage plus up to 4 **contact, imprint, about or team pages** of each site.
- 🎯 Filters at no extra charge: only places with a website, only leads with an email, minimum star rating.
- 💸 **One flat price per lead**: no add-on events for contacts, filters or social links.

### What does Google Maps Email & Social Leads Scraper do?

1. It runs each search (for example `dentist Austin, TX`) on Google Maps and pages through the result list, 20 places per page.
2. It skips duplicates (the same place found by two searches) and places that fail your filters.
3. It opens the business website, finds the best contact pages on that site, and extracts emails, `tel:` phone numbers and social profile links.
4. It saves one lead row and charges one lead event. Then it moves to the next place.

If a website is down or refuses automated visitors, you still get the Google Maps data. The `websiteStatus` field then says `failed` or `blocked`, so you can see why the contact fields are empty.

### Who is it for?

- **Agencies and freelancers** building outreach lists of local businesses (web design, SEO, marketing, bookkeeping).
- **SaaS sales teams** prospecting a vertical such as dentists, gyms, salons, law firms or HVAC companies in many cities.
- **Lead-generation sellers** refreshing niche lists on a schedule.
- **Recruiters and partnership managers** who need the official contact address and social pages of local companies.
- **Market researchers** who want to know how many businesses in an area have a website, an email or an Instagram account.

### Why use this Actor?

- **Contacts included in the lead price.** Some Google Maps scrapers charge one event for the place and another for contact enrichment. Here one lead event covers the Maps data, the website scan and all social links.
- **Several cities per run.** Every search term is combined with every location, up to 50 combinations per run.
- **Transparent failures.** Every row has `websiteStatus`, `websiteError` and `pagesScanned`, so empty contact fields are explained.
- **Clean URLs.** `utm_*` and other tracking parameters are removed from websites, and phone numbers are deduplicated.
- **Safe by default.** The Actor visits only public websites. It checks the address each connection actually uses, including after redirects, so private-network addresses are refused. Every page is capped at 1.5 MB and 12 seconds.

### What data can you extract from Google Maps businesses?

| Field | Example | Source |
|---|---|---|
| `name` | 38th Street Dental | Google Maps |
| `category`, `categories` | Dentist, Cosmetic dentist… | Google Maps |
| `address`, `neighborhood`, `cityRegion` | 1500 W 38th St Ste 56, Austin, TX 78731 | Google Maps |
| `phone`, `phoneInternational` | (737) 260-5523, +17372605523 | Google Maps |
| `website`, `domain` | https://www.myaustindds.com/ | Google Maps |
| `rating`, `reviewsCount` | 5, 734 | Google Maps |
| `latitude`, `longitude` | 30.3082, -97.7488 | Google Maps |
| `placeId`, `cid`, `fid`, `googleMapsUrl` | ChIJe5fsvYXKRIYR… | Google Maps |
| `primaryEmail`, `emails` | info@atxfamilydental.com | Business website |
| `websitePhones` | +15124586222 | Website `tel:` links |
| `facebookUrl`, `instagramUrl`, `linkedinUrl`, `twitterUrl`, `youtubeUrl`, `tiktokUrl`, `pinterestUrl` | https://www.instagram.com/38thstreetdental | Website links |
| `contactPageUrl`, `pagesScanned` | https://www.myaustindds.com/contact/ | Website scan |
| `websiteStatus`, `websiteError` | scanned / failed / blocked / no_website | Website scan |
| `searchString`, `searchLocation`, `rank` | dentist, Austin, TX, 3 | Your input |

### How much does it cost to scrape Google Maps business emails and social links?

The Actor uses pay-per-event pricing. You pay a small fee per run plus one fee per saved lead. Website visits, filters and social links have no separate charge.

| Event | Price (Bronze plan) |
|---|---|
| Run start | $0.003 per run |
| Business lead saved | $0.00336 per lead |

Example costs on the Bronze plan:

| Leads saved | Cost |
|---|---|
| 10 leads | $0.037 |
| 100 leads | $0.34 |
| 1,000 leads | $3.36 |

Higher Apify plans pay less per lead (Silver $0.0026208, Gold and above $0.002016). The Free plan pays $0.003864 per lead. You never pay for places skipped by your filters, duplicates, or leads dropped by the email-only filter. Set **Maximum leads per run** or a maximum charge per run to cap your spend.

### How to scrape emails from Google Maps businesses

1. Open the Actor in Apify Console and click **Try for free**.
2. Under **Search terms**, enter one or more business types, for example `dentist` or `plumber`.
3. Under **Locations**, enter the cities or areas, for example `Austin, TX` and `Dallas, TX`.
4. Set **Maximum leads per search** and **Maximum leads per run**.
5. Optional: turn on **Only leads with an email** or set a **Minimum Google rating**.
6. Click **Start**. Download the leads as CSV, Excel or JSON, or send them to your CRM.

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `searchStringsArray` | string\[] | required | Business keywords. Each is combined with each location. |
| `locationQueries` | string\[] | empty | Cities or areas appended to each search term. Leave empty if your search terms already include the place. |
| `maxPlacesPerSearch` | integer | 20 | Leads saved per search term × location, after filters (1–500). |
| `maxItems` | integer | 100 | Leads saved per run across all searches (1–5,000). |
| `onlyWithWebsite` | boolean | true | Skip places without a website. |
| `onlyWithEmail` | boolean | false | Save only leads where an email was found; other places are not billed. Searches where very few websites have an email stop early (see FAQ). |
| `minRating` | number | none | Skip places rated below this value, and places with no rating. |
| `maxPagesPerWebsite` | integer | 3 | Homepage plus up to N−1 contact/imprint/about/team pages (1–5). |
| `language` | string | en | Google interface language (en, de, es, fr, pt-BR…). |
| `countryCode` | string | us | Google region context (us, gb, de…). This does not restrict results to that country. |

Example input:

```json
{
    "searchStringsArray": ["dentist", "orthodontist"],
    "locationQueries": ["Austin, TX", "San Antonio, TX"],
    "maxPlacesPerSearch": 50,
    "maxItems": 200,
    "onlyWithEmail": true
}
```

### Output example

A real lead row (shortened):

```json
{
    "name": "38th Street Dental",
    "category": "Dentist",
    "address": "1500 W 38th St Ste 56, Austin, TX 78731",
    "phone": "(737) 260-5523",
    "phoneInternational": "+17372605523",
    "website": "https://www.myaustindds.com/",
    "domain": "myaustindds.com",
    "rating": 5,
    "reviewsCount": 734,
    "primaryEmail": null,
    "emails": [],
    "websitePhones": ["+15124586222"],
    "facebookUrl": "https://www.facebook.com/38th-Street-Dental-201580976550317/",
    "instagramUrl": "https://www.instagram.com/38thstreetdental",
    "linkedinUrl": "https://www.linkedin.com/company/38th-street-dental",
    "twitterUrl": null,
    "youtubeUrl": null,
    "tiktokUrl": null,
    "pinterestUrl": null,
    "socialLinksCount": 3,
    "contactPageUrl": "https://www.myaustindds.com/contact/",
    "pagesScanned": ["https://www.myaustindds.com/", "https://www.myaustindds.com/contact/", "https://www.myaustindds.com/locations/austin-office/"],
    "websiteStatus": "scanned",
    "websiteError": null,
    "placeId": "ChIJe5fsvYXKRIYRwmoXXLCat2Q",
    "cid": "7257439406780803778",
    "googleMapsUrl": "https://www.google.com/maps?cid=7257439406780803778",
    "searchString": "dentist",
    "searchLocation": "Austin, TX",
    "rank": 3,
    "scrapedAt": "2026-10-31T07:09:25.492Z"
}
```

The dataset has two views: **Leads** (contact columns first) and **Social links**. The run's `OUTPUT` record in the key-value store holds counters: searches, places seen, duplicates, filtered places, websites scanned/failed and why the run stopped. `searchesFailed` counts searches whose first Google Maps page failed, `searchesIncomplete` counts searches that stopped on a later page (rows already saved are kept), and `searchErrors` lists each failure with its page number.

### Tips and limits

- **How many results per search?** Google Maps usually lists about 120–200 places for one search. To get more, split the area into neighborhoods or nearby towns, for example `plumber Brooklyn` and `plumber Queens` instead of `plumber New York`.
- **Google Maps changes are reported, not hidden.** The Actor only accepts the Google Maps response layouts it recognizes. An unrecognized layout counts as a failed search page instead of an empty result, and the run fails when every search fails. A real "no results" answer still finishes successfully with 0 rows.
- **Places without a category.** A few places, for example some hotel brands, have no Google category. They are still saved, with `category` set to null.
- **Location is search text.** Google decides relevance and may include nearby places outside the city you typed.
- **Not every business publishes an email.** In our tests, 50–70% of businesses with a website showed an email, and 70–80% linked at least one social profile. This depends a lot on the industry and country.
- **Blocked websites.** Some sites use bot protection (for example Cloudflare challenges) or are offline. Those rows have `websiteStatus: "blocked"` or `"failed"` and keep the Google Maps data.
- **Emails are not verified.** The Actor reports the addresses published on the site. Check deliverability with an email verifier before a campaign.
- Websites built fully in client-side JavaScript can hide emails from a plain page download. Use `contactPageUrl` to check those sites manually.

### Integrations

- **Google Sheets / Excel**: export the **Leads** view as CSV, or use the Apify Google Sheets integration to append new leads after each scheduled run.
- **HubSpot, Pipedrive, Salesforce**: connect through Make, Zapier or n8n. Map `name`, `primaryEmail`, `phoneInternational`, `website` and `address` to company fields.
- **Cold email tools** (Instantly, Lemlist, Smartlead): filter rows with `primaryEmail`, verify them with our [Bulk Email MX Verifier](https://apify.com/automation-lab/email-mx-verifier), then import.
- **Scheduled refresh**: create an Apify schedule that runs the same searches every week and send new rows to a webhook.

### Use the Actor with the Apify API

cURL:

```bash
curl -X POST "https://api.apify.com/v2/acts/automation-lab~google-maps-business-website-email-and-social-contact-leads/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchStringsArray":["dentist"],"locationQueries":["Austin, TX"],"maxItems":20}'
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('automation-lab/google-maps-business-website-email-and-social-contact-leads').call({
    searchStringsArray: ['dentist'],
    locationQueries: ['Austin, TX'],
    maxItems: 20,
    onlyWithEmail: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((lead) => [lead.name, lead.primaryEmail, lead.instagramUrl]));
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("automation-lab/google-maps-business-website-email-and-social-contact-leads").call(run_input={
    "searchStringsArray": ["dentist"],
    "locationQueries": ["Austin, TX"],
    "maxItems": 20,
})
for lead in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(lead["name"], lead["primaryEmail"], lead["facebookUrl"])
```

### Use with AI agents through MCP

Connect the Actor to Claude Code, Claude Desktop, Cursor or another MCP client through the Apify MCP server:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/google-maps-business-website-email-and-social-contact-leads"
```

Claude Desktop, Cursor and VS Code: add this configuration to the client's MCP settings and authenticate with your own Apify token:

```json
{
    "mcpServers": {
        "apify": {
            "url": "https://mcp.apify.com?tools=automation-lab/google-maps-business-website-email-and-social-contact-leads"
        }
    }
}
```

Selecting this Actor also adds Apify's helper tools for runs and datasets (`get-actor-run`, `get-dataset-items`, `get-key-value-store-record`, `abort-actor-run`). A good agent workflow:

1. Start one run and keep its run ID. `call-actor` waits at most about 45 seconds, so a larger lead list may still be running.
2. If the run is not finished, check the same run with `get-actor-run` using short backoff (2, 4, 8 seconds) and a total deadline. Do not start a second run just to poll.
3. When it succeeds, read the dataset with `get-dataset-items` in pages: `offset` 0, then 20, 40 and so on, with `limit` 20 and only the fields you need (`fields`: `name,primaryEmail,website,instagramUrl`).
4. Set a total budget for the chat before you start, for example at most 100 rows or 64 KB of returned data and a 5-minute deadline. Stop paging when you reach it.
5. If you stop early, or the run ended with `stopReason` other than `completed` in its `OUTPUT` record, say that the results are partial. Give the user the dataset ID so the full list can be exported from Apify storage instead of being pasted into the chat.

Example prompts:

- "Find 30 coffee shops in Lisbon with an Instagram account and list their emails."
- "Get roofing contractors in Phoenix, AZ rated 4.5 or higher that publish an email address."
- "Build a list of dentists in Austin and San Antonio with website, phone and Facebook page."

### Is it legal to scrape emails from Google Maps and business websites?

The Actor collects publicly shown business listing data and contact details that businesses publish on their own websites. It does not log in, solve CAPTCHAs or access private data. Some business emails name a person (for example `jane.doe@clinic.com`). That is personal data under GDPR and similar laws. You are responsible for having a lawful basis to process it, and for following anti-spam rules such as CAN-SPAM, CASL and the ePrivacy Directive, including opt-out handling. Respect the websites' terms. When in doubt, ask a lawyer. This Actor is not affiliated with or endorsed by Google.

**Data handling.** The Actor uses no AI or model provider. Leads are stored only in your Apify run storage, under your account's retention settings. When a run fails, sanitized diagnostic input, the error and the Actor/build/run IDs are sent to our private GlitchTip service so we can fix the problem. Secret fields and URL query strings are removed, and reports are deleted after 30 days.

### FAQ

**Why do some leads have no email?**
The business did not publish an email on the scanned pages, used a contact form instead, or the site was offline or blocked (see `websiteStatus` and `websiteError`). Increase **Pages to scan per website** to 4–5 to read more pages, or turn on **Only leads with an email** so you pay only for leads with an email.

**Why did I get fewer leads than I asked for?**
Google Maps ran out of results for that search, or your filters removed places. Check the `OUTPUT` record for `placesSeen`, `filteredOut` and `duplicates`. Use smaller areas or more keyword variations for more results.

**Why did an email-only search stop early?**
With **Only leads with an email**, the Actor visits websites that may not be saved. When a search has visited at least 40 websites and found fewer than one saved lead per 15 visits, it moves to the next search. The whole run stops the same way after 100 visits (`stopReason: "lowEmailYield"`). The `OUTPUT` record shows `websiteVisits` and `searchesStoppedLowEmailYield`. Try a business type that usually publishes emails, or turn the filter off.

**Is the same business charged twice when two searches find it?**
No. Places are deduplicated across the whole run by their Google Maps ID. Only the first search that finds a place saves it.

**Can I get leads without a website?**
Yes. Turn off **Only places with a website**. Those rows have `websiteStatus: "no_website"` and are charged as normal leads.

**Does it scrape Google reviews or photos?**
No. For reviews use our [Google Maps Place Reviews Scraper](https://apify.com/automation-lab/google-maps-place-reviews-scraper).

### Related Actors

- [Google Maps Business Directory Scraper](https://apify.com/automation-lab/google-maps-keyword-location-directory): cheaper Google Maps place lists without the website scan.
- [Website Contact Finder](https://apify.com/automation-lab/website-contact-finder): extract contacts from your own list of website URLs.
- [Website Email Extractor](https://apify.com/automation-lab/website-email-extractor): crawl websites you supply for email addresses.
- [Bulk Email MX Verifier](https://apify.com/automation-lab/email-mx-verifier): check the mail servers of extracted email domains before outreach.
- [Google Maps Place Reviews Scraper](https://apify.com/automation-lab/google-maps-place-reviews-scraper): collect reviews for the places you found.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/google-maps-business-website-email-and-social-contact-leads/changelog.md

# Actor input Schema

## `searchStringsArray` (type: `array`):

Business keywords or categories to search on Google Maps, for example "dentist", "roofing contractor" or "coffee shop". 1–50 nonempty strings of at most 150 characters; trimmed, whitespace-normalized and deduplicated. Google decides relevance, so results are related businesses, not exact-name matches. If you also enter locations, every search term is combined with every location (at most 50 combinations).

## `locationQueries` (type: `array`):

Optional cities, regions or neighborhoods, for example "Austin, TX" or "Berlin Mitte". Each location is appended to each search term as search text ("dentist Austin, TX"); it is not a strict map boundary, so Google can include nearby places. Leave empty to put the location inside the search term yourself.

## `maxPlacesPerSearch` (type: `integer`):

Maximum leads saved for each search term × location combination, after filters and deduplication. Google Maps usually lists up to about 120–200 places for one search; for more leads, split the area into smaller locations. Fewer results are normal for narrow searches.

## `maxItems` (type: `integer`):

Global cap on saved leads across all searches. Searches run in input order (each term with each location) and the run stops once this many leads are saved. Your maximum charge per run setting can stop the run earlier.

## `onlyWithWebsite` (type: `boolean`):

When on (default), places without a website on Google Maps are skipped and not charged. Turn off to also save places that only have a phone number and address; those rows have websiteStatus "no_website" and no emails or social links.

## `onlyWithEmail` (type: `boolean`):

When on, a lead is saved and charged only if at least one email was found on its website. Websites are still visited for every place with a website, so runs with this filter take longer per saved lead. A search where fewer than 1 in 15 visited websites has an email stops after 40 visits, and the whole run stops the same way after 100 visits.

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

Optional. Skip places whose Google star rating is below this value (for example 4). Places without any rating are also skipped when this is set. Leave empty or 0 for no rating filter.

## `maxPagesPerWebsite` (type: `integer`):

How many pages of each business website to read: the homepage plus up to this many minus one same-site pages whose links look like contact, imprint, about or team pages. 1 reads the homepage only (fastest). Each page is limited to 1.5 MB and 12 seconds.

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

Google interface language code for category names and addresses, for example en, de, es, fr or pt-BR.

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

Two-letter Google region code used as search context, for example us, gb, de or ca. It does not restrict results to that country; put the city or country in Locations for that.

## Actor input object example

```json
{
  "searchStringsArray": [
    "dentist"
  ],
  "locationQueries": [
    "Austin, TX"
  ],
  "maxPlacesPerSearch": 20,
  "maxItems": 20,
  "onlyWithWebsite": true,
  "onlyWithEmail": false,
  "maxPagesPerWebsite": 3,
  "language": "en",
  "countryCode": "us"
}
```

# Actor output Schema

## `leads` (type: `string`):

All saved leads, contact columns first: name, primary email, emails, phone, website, social links, address, rating and website scan status.

## `socials` (type: `string`):

The same leads showing only website and the seven social profile link columns.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchStringsArray": [
        "dentist"
    ],
    "locationQueries": [
        "Austin, TX"
    ],
    "maxPlacesPerSearch": 20,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/google-maps-business-website-email-and-social-contact-leads").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchStringsArray": ["dentist"],
    "locationQueries": ["Austin, TX"],
    "maxPlacesPerSearch": 20,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/google-maps-business-website-email-and-social-contact-leads").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchStringsArray": [
    "dentist"
  ],
  "locationQueries": [
    "Austin, TX"
  ],
  "maxPlacesPerSearch": 20,
  "maxItems": 20
}' |
apify call automation-lab/google-maps-business-website-email-and-social-contact-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/google-maps-business-website-email-and-social-contact-leads"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/GV5wV70LuogrlO47X/builds/JkZpmWWnMfCv3RbeS/openapi.json
