Apify Maps Discovery
Turn a vertical + a place into a clean prospect list, scraped from Google Maps
via the maintained Apify Compass actor (compass~crawler-google-places). We
wrap the best existing actor rather than build our own scraper - Compass already
handles proxies, anti-bot, and layout drift, and offers built-in email/contact
enrichment. Output is the normalized Zevenue ProspectRecord schema (JSON + CSV).
Process
- Confirm inputs. Need
search_term (the vertical) and geo (the area).
Defaults: max_results=200, min_rating=4.0. Ask only if missing/ambiguous.
- Estimate cost and gate. The skill prints an estimate before calling the
API. If it exceeds $10, it requires explicit confirmation (
--yes or an
interactive y/N). Never bypass this silently.
- Run the actor. Calls the Compass sync endpoint with the built input
(
searchStringsArray, locationQuery, maxCrawledPlacesPerSearch,
placeMinimumStars). Add enrichment only when asked (see flags).
- Normalize. Each raw place is mapped to the shared 15-field ProspectRecord
so it's comparable with the Outscraper / Google Places skills.
- Write the run folder. Writes
runs/<run-id>/ per headless-gtm-shared/CONVENTIONS.md:
records.jsonl (the shared chain format 03+ consume - company, domain,
person, plus the firmographic fields), tracker.json, meta.json (count +
spend estimate), prospects.json (all 15 ProspectRecord fields), and
records.csv for humans. Report the row count and path; downstream skills
read runs/<run-id>/records.jsonl (e.g. 05's collect --records).
Usage
# install deps once (use a venv - system Python is externally-managed)
pip install -r ../headless-gtm-shared/requirements.txt
export APIFY_API_TOKEN=... # console.apify.com → Settings → Integrations
python discover.py \
--search-term "yoga studios" \
--geo "California" \
--max-results 200 \
--min-rating 4.0 \
[--include-emails] [--with-review-dates] \
[--out DIR] [--estimate-only] [--yes]
Inputs
| Flag |
Maps to |
Default |
--search-term |
actor searchStringsArray |
required |
--geo |
actor locationQuery |
required |
--max-results |
maxCrawledPlacesPerSearch |
200 |
--min-rating |
placeMinimumStars |
4.0 |
--include-emails |
scrapeContacts (+$2/1K) |
off |
--with-review-dates |
scrapePlaceDetailPage (extra cost) |
off |
--estimate-only |
print cost, no API call |
- |
--yes |
skip the >$10 prompt |
- |
Output (normalized ProspectRecord)
name, domain, website, phone, full_address, city, region, rating, reviews_count, latitude, longitude, place_id, category, last_review_date, source → see ../headless-gtm-shared/schema.py.
Cost
| Mode |
Flags |
Est. price |
| Discovery |
(default) |
~$2.10 / 1K |
| Discovery + emails |
--include-emails |
~$4.10 / 1K |
| Typical 2K-record run |
either |
$4–8 |
last_review_date is off by default - it needs Compass's per-place detail
page (--with-review-dates), which adds cost and latency. The cheap default pull
leaves it blank. Edit the cost constants at the top of discover.py if your
account pricing differs from the doc's $2.10/1K figure.
What good output looks like
- Every row has at least
name + (domain or phone) - usable for outreach.
domain is a bare host (no scheme/www) so it dedups cleanly downstream.
- Closed businesses are skipped (
skipClosedPlaces), ratings ≥ min_rating.
- Row count is in the ballpark of what the geo realistically contains - a tiny
count usually means the
geo was too narrow or the term too specific.
Scope boundaries (what this skill does NOT do)
- Not directory-only businesses with no Maps listing → use the extraction
skill.
- Not tech-stack-defined ICPs (e.g. "stores on Shopify") → signal layer.
- Not qualitative web-observable ICPs → separate ICP discovery.
- Not email validation or company firmographics → downstream enrichment /
validation skills.
- Outscraper is kept proprietary for client work; Compass is the public/
distributable Maps tool chosen here. Google Places was rejected (60-result
per-query cap + Enterprise-tier cost for contact fields).
Reference
Deeper detail lives in the reference/ folder - load the file you need:
- reference/compass-actor.md - actor ID, sync &
async endpoints, the full input schema (every field + type), raw output fields,
and limits.
- reference/output-schema.md - the normalized
15-field ProspectRecord, the raw→normalized field map, domain-extraction and
dedup-key rules, and JSON/CSV samples.
- reference/cost-model.md - pricing per mode, how the
$10 gate is computed, worked cost examples, and how to edit the constants.
- reference/verticals-and-geo.md - recommended
search_term phrasings per Zevenue vertical, geo formatting, and how to size
max_results / min_rating.
- reference/examples.md - copy-paste recipes for common
jobs (quick test, full 2K list with emails, multi-city, estimate-only dry run).
- reference/troubleshooting.md - empty results,
auth/token errors, timeouts, rate limits, the 300s sync cap, and field-name
caveats.
1---2name: 02-apify-maps-discover3description: Find local-business (SMB) prospects via Google Maps using the Apify Compass actor. Use for Zevenue Step-2 Discovery (Vertical) when the ICP is Maps-addressable - laundromats, salons, HVAC, yoga studios, gyms, clinics - and you need a normalized list of name, domain, address, phone, rating, and review count ready for enrichment. Estimates cost and prompts before any run over $10. Requires APIFY_API_TOKEN.4---56# Apify Maps Discovery78Turn a vertical + a place into a clean prospect list, scraped from Google Maps9via the maintained **Apify Compass** actor (`compass~crawler-google-places`). We10wrap the best existing actor rather than build our own scraper - Compass already11handles proxies, anti-bot, and layout drift, and offers built-in email/contact12enrichment. Output is the normalized Zevenue ProspectRecord schema (JSON + CSV).1314## Process151. **Confirm inputs.** Need `search_term` (the vertical) and `geo` (the area).16 Defaults: `max_results=200`, `min_rating=4.0`. Ask only if missing/ambiguous.172. **Estimate cost and gate.** The skill prints an estimate before calling the18 API. If it exceeds **$10**, it requires explicit confirmation (`--yes` or an19 interactive y/N). Never bypass this silently.203. **Run the actor.** Calls the Compass sync endpoint with the built input21 (`searchStringsArray`, `locationQuery`, `maxCrawledPlacesPerSearch`,22 `placeMinimumStars`). Add enrichment only when asked (see flags).234. **Normalize.** Each raw place is mapped to the shared 15-field ProspectRecord24 so it's comparable with the Outscraper / Google Places skills.255. **Write the run folder.** Writes `runs/<run-id>/` per `headless-gtm-shared/CONVENTIONS.md`:26 `records.jsonl` (the shared chain format 03+ consume - `company`, `domain`,27 `person`, plus the firmographic fields), `tracker.json`, `meta.json` (count +28 spend estimate), `prospects.json` (all 15 ProspectRecord fields), and29 `records.csv` for humans. Report the row count and path; downstream skills30 read `runs/<run-id>/records.jsonl` (e.g. 05's `collect --records`).3132## Usage33```bash34# install deps once (use a venv - system Python is externally-managed)35pip install -r ../headless-gtm-shared/requirements.txt36export APIFY_API_TOKEN=... # console.apify.com → Settings → Integrations3738python discover.py \39 --search-term "yoga studios" \40 --geo "California" \41 --max-results 200 \42 --min-rating 4.0 \43 [--include-emails] [--with-review-dates] \44 [--out DIR] [--estimate-only] [--yes]45```4647### Inputs48| Flag | Maps to | Default |49|---|---|---|50| `--search-term` | actor `searchStringsArray` | required |51| `--geo` | actor `locationQuery` | required |52| `--max-results` | `maxCrawledPlacesPerSearch` | 200 |53| `--min-rating` | `placeMinimumStars` | 4.0 |54| `--include-emails` | `scrapeContacts` (+$2/1K) | off |55| `--with-review-dates` | `scrapePlaceDetailPage` (extra cost) | off |56| `--estimate-only` | print cost, no API call | - |57| `--yes` | skip the >$10 prompt | - |5859### Output (normalized ProspectRecord)60`name, domain, website, phone, full_address, city, region, rating,61reviews_count, latitude, longitude, place_id, category, last_review_date,62source` → see [../headless-gtm-shared/schema.py](../headless-gtm-shared/schema.py).6364## Cost65| Mode | Flags | Est. price |66|---|---|---|67| Discovery | (default) | ~$2.10 / 1K |68| Discovery + emails | `--include-emails` | ~$4.10 / 1K |69| Typical 2K-record run | either | **$4–8** |7071`last_review_date` is **off by default** - it needs Compass's per-place detail72page (`--with-review-dates`), which adds cost and latency. The cheap default pull73leaves it blank. Edit the cost constants at the top of `discover.py` if your74account pricing differs from the doc's $2.10/1K figure.7576## What good output looks like77- Every row has at least `name` + (`domain` or `phone`) - usable for outreach.78- `domain` is a bare host (no scheme/www) so it dedups cleanly downstream.79- Closed businesses are skipped (`skipClosedPlaces`), ratings ≥ `min_rating`.80- Row count is in the ballpark of what the geo realistically contains - a tiny81 count usually means the `geo` was too narrow or the term too specific.8283## Scope boundaries (what this skill does NOT do)84- **Not** directory-only businesses with no Maps listing → use the extraction85 skill.86- **Not** tech-stack-defined ICPs (e.g. "stores on Shopify") → signal layer.87- **Not** qualitative web-observable ICPs → separate ICP discovery.88- **Not** email validation or company firmographics → downstream enrichment /89 validation skills.90- Outscraper is kept proprietary for client work; **Compass is the public/91 distributable Maps tool** chosen here. Google Places was rejected (60-result92 per-query cap + Enterprise-tier cost for contact fields).9394## Reference95Deeper detail lives in the `reference/` folder - load the file you need:9697- [reference/compass-actor.md](reference/compass-actor.md) - actor ID, sync &98 async endpoints, the full input schema (every field + type), raw output fields,99 and limits.100- [reference/output-schema.md](reference/output-schema.md) - the normalized101 15-field ProspectRecord, the raw→normalized field map, domain-extraction and102 dedup-key rules, and JSON/CSV samples.103- [reference/cost-model.md](reference/cost-model.md) - pricing per mode, how the104 $10 gate is computed, worked cost examples, and how to edit the constants.105- [reference/verticals-and-geo.md](reference/verticals-and-geo.md) - recommended106 `search_term` phrasings per Zevenue vertical, `geo` formatting, and how to size107 `max_results` / `min_rating`.108- [reference/examples.md](reference/examples.md) - copy-paste recipes for common109 jobs (quick test, full 2K list with emails, multi-city, estimate-only dry run).110- [reference/troubleshooting.md](reference/troubleshooting.md) - empty results,111 auth/token errors, timeouts, rate limits, the 300s sync cap, and field-name112 caveats.