Overview
The MapsHarvest API lets you start and manage Google Maps scrape jobs programmatically — no browser required. You can launch scrapes, poll for progress, download results in CSV, XLSX, or JSON, and manage your API keys.
API access is available on the Growth plan and above. All endpoints accept and return JSON. Dates are ISO 8601 (UTC).
https://api.mapsharvest.comAuthentication
Every request must include an Authorization header with a Bearer token. Two token types are accepted:
Generated from the dashboard → Integrations → API access. Recommended for server-side integrations.
curl https://api.mapsharvest.com/jobs/abc123 \
-H "Authorization: Bearer mh_live_your_key_here"Short-lived token from your Supabase session. Used by the dashboard itself. Fine for quick scripts.
curl https://api.mapsharvest.com/jobs/abc123 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR..."Rate limits
Each account is limited to 5 concurrent scrape jobs. Starting a sixth while five are running returns 429 Too Many Requests. There is no per-minute rate limit on read endpoints (/jobs, /fields, etc.).
| Limit | Value |
|---|---|
Concurrent scrape jobs | 5 per account |
API keys per account | 10 active keys |
Max results per city | 120 leads |
Webhook payload size | Unbounded (streamed) |
POST /scrape
POSTStart a new scrape job. Returns a job_id immediately — the scrape runs asynchronously.
| Field | Type | Required | Description |
|---|---|---|---|
query | string | ✓ | Business type to search (e.g. "dentists") |
states | string | ✓ | Comma-separated US state names (e.g. "Florida,Texas") |
cities | string | Comma-separated city names. Omit to scrape all cities in the states. | |
max_results_per_city | integer | Cap per city. Default 120, max 120. | |
workers | integer | Parallel workers. Default 1. Ignored — server enforces 1. | |
preset | string | Field preset: essentials · standard · full · custom | |
fields | string | Comma-separated field keys when preset=custom. | |
category_filter | string | Strict category to match (exact GMB category name). | |
related_categories | string | Comma-separated additional categories to include. | |
min_rating | number | Only keep businesses with rating ≥ this value. | |
min_reviews | integer | Only keep businesses with review count ≥ this value. | |
max_reviews | integer | Only keep businesses with review count ≤ this value. | |
require_website | boolean | true = only businesses with a website. | |
exclude_website | boolean | true = only businesses without a website. | |
require_phone | boolean | true = only businesses with a phone number. | |
exclude_phone | boolean | true = only businesses without a phone number. | |
webhook_url | string | URL to POST results to when the job finishes. | |
webhook_trigger | string | completed · stopped · any · manual (default manual). | |
webhook_fields | string[] | Array of field names to include in the webhook payload. | |
campaign_id | string (uuid) | Assign this scrape to a campaign. | |
name | string | Human-readable label for this scrape run (max 200 chars). | |
resume_job_id | string | ID of a previous job to resume from where it left off. |
{
"job_id": "a7f3c2d1e8b94f56...",
"status": "queued"
}curl -X POST https://api.mapsharvest.com/scrape \
-H "Authorization: Bearer mh_live_..." \
-H "Content-Type: application/json" \
-d '{
"query": "dentists",
"states": "Florida,Texas",
"preset": "standard",
"min_rating": 4,
"webhook_url": "https://hooks.zapier.com/hooks/catch/...",
"webhook_trigger": "completed"
}'GET /jobs/{id}
GETPoll the live status of a scrape job.
| Field | Type | Description |
|---|---|---|
job_id | string | Unique job identifier |
status | string | queued · running · stopping · stopped · completed · failed |
rows_written | integer | Leads collected so far |
cities_done | integer | Cities fully scraped |
cities_total | integer | Total cities to scrape |
cities_remaining | integer | Cities left |
last_city | string | null | Most recently scraped city |
started_at | number | Unix timestamp when the job started |
eta_seconds | number | null | Estimated seconds remaining |
error | string | null | Error message if status is failed |
curl https://api.mapsharvest.com/jobs/a7f3c2d1e8b94f56 \
-H "Authorization: Bearer mh_live_..."GET /jobs/{id}/download
GETDownload the results of a completed (or stopped) job. Format is controlled by theformat query parameter. Available formats depend on your plan.
| Param | Values | Default |
|---|---|---|
format | csv · xlsx · json | csv |
# CSV (all plans)
curl "https://api.mapsharvest.com/jobs/a7f3c2d1e8b94f56/download?format=csv" \
-H "Authorization: Bearer mh_live_..." \
-o leads.csv
# JSON (Growth+ only)
curl "https://api.mapsharvest.com/jobs/a7f3c2d1e8b94f56/download?format=json" \
-H "Authorization: Bearer mh_live_..." \
-o leads.jsonPOST /jobs/{id}/stop
POSTSignal a running job to stop gracefully. The job transitions to stopping and then stopped. Any leads already collected are preserved and downloadable.
curl -X POST https://api.mapsharvest.com/jobs/a7f3c2d1e8b94f56/stop \
-H "Authorization: Bearer mh_live_..."GET /fields
GETReturns the full catalog of available data fields, grouped by tier, along with preset definitions and their credit costs.
curl https://api.mapsharvest.com/fields \
-H "Authorization: Bearer mh_live_..."GET /cities
GETReturns all cities available for the given states. Used to build the city picker in the dashboard.
| Param | Type | Description |
|---|---|---|
states | string | Comma-separated state names (e.g. states=Florida,Texas) |
curl "https://api.mapsharvest.com/cities?states=Florida,Texas" \
-H "Authorization: Bearer mh_live_..."GET /api-keys
GETList all API keys for the authenticated account. Key hashes are never returned.
{
"keys": [
{
"id": "uuid",
"name": "My Zapier integration",
"key_prefix": "mh_live_a1b2",
"created_at": "2026-05-24T10:00:00Z",
"last_used_at": "2026-05-24T14:32:00Z",
"revoked_at": null
}
]
}POST /api-keys
POSTCreate a new API key. The full raw key is returned once only in the raw_key field and is never stored in plaintext. Save it immediately.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | Label for this key (max 100 chars). e.g. "Zapier integration" |
{
"id": "uuid",
"name": "My Zapier integration",
"key_prefix": "mh_live_a1b2",
"created_at": "2026-05-24T10:00:00Z",
"raw_key": "mh_live_a1b2c3d4e5f6..."
}DELETE /api-keys/{id}
DELETEPermanently delete an API key. Any integrations using it will stop authenticating immediately. Returns 204 No Content on success.
curl -X DELETE https://api.mapsharvest.com/api-keys/key-uuid-here \
-H "Authorization: Bearer mh_live_..."Fields reference
Which fields are available depends on your plan's field tier. Higher tiers include all lower-tier fields.
| Field | Type | Description |
|---|---|---|
name | string | Business name |
category | string | Primary Google Business category |
phone | string | Phone number |
address | string | Full street address |
city | string | City |
state | string | State |
rating | number | Average star rating (1–5) |
review_count | integer | Total number of reviews |
google_maps_url | string | Direct Google Maps URL |
| Field | Type | Description |
|---|---|---|
website | string | Business website URL |
website_domain | string | Root domain (e.g. example.com) |
hours | string | Opening hours (JSON string) |
price_level | string | $ $$ $$$ $$$$ |
| Field | Type | Description |
|---|---|---|
temporarily_closed | boolean | Whether the business is temporarily closed |
description | string | Business description from Google |
plus_code | string | Google Plus Code location |
query | string | Search query used to find this business |
scraped_at | string | ISO 8601 timestamp of when the row was scraped |
Error codes
| Status | Meaning |
|---|---|
400 Bad Request | Missing or invalid request parameters. |
401 Unauthorized | Missing, invalid, or revoked auth token. |
403 Forbidden | Your plan does not include this feature. |
404 Not Found | Job or resource does not exist (or belongs to another user). |
429 Too Many Requests | 5 concurrent jobs already running, or 10 API keys at limit. |
500 Internal Server Error | Something went wrong on our end. Retry after a moment. |
All error responses include a JSON body with a detail field explaining the reason:
{ "detail": "API keys require a Growth plan or higher" }Code examples
import requests, time
API_BASE = "https://api.mapsharvest.com"
API_KEY = "mh_live_your_key_here"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 1. Start the scrape
resp = requests.post(f"{API_BASE}/scrape", headers=HEADERS, json={
"query": "plumbers",
"states": "California",
"preset": "standard",
"min_rating": 4.0,
})
job_id = resp.json()["job_id"]
print(f"Job started: {job_id}")
# 2. Poll until done
while True:
status = requests.get(f"{API_BASE}/jobs/{job_id}", headers=HEADERS).json()
print(f" {status['status']} — {status['rows_written']} leads")
if status["status"] in ("completed", "stopped", "failed"):
break
time.sleep(5)
# 3. Download CSV
csv = requests.get(f"{API_BASE}/jobs/{job_id}/download?format=csv", headers=HEADERS)
with open("leads.csv", "wb") as f:
f.write(csv.content)
print("Saved leads.csv")const API_BASE = "https://api.mapsharvest.com";
const API_KEY = "mh_live_your_key_here";
const headers = { Authorization: `Bearer ${API_KEY}` };
async function run() {
// 1. Start scrape
const start = await fetch(`${API_BASE}/scrape`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ query: "plumbers", states: "California", preset: "standard" }),
});
const { job_id } = await start.json();
console.log("Job started:", job_id);
// 2. Poll until done
let done = false;
while (!done) {
await new Promise(r => setTimeout(r, 5000));
const s = await (await fetch(`${API_BASE}/jobs/${job_id}`, { headers })).json();
console.log(` ${s.status} — ${s.rows_written} leads`);
if (["completed","stopped","failed"].includes(s.status)) done = true;
}
// 3. Download CSV
const csv = await fetch(`${API_BASE}/jobs/${job_id}/download?format=csv`, { headers });
require("fs").writeFileSync("leads.csv", Buffer.from(await csv.arrayBuffer()));
console.log("Saved leads.csv");
}
run();# Start scrape, poll every 5s, download when done
JOB=$(curl -s -X POST https://api.mapsharvest.com/scrape \
-H "Authorization: Bearer mh_live_..." \
-H "Content-Type: application/json" \
-d '{"query":"dentists","states":"Florida","preset":"essentials"}' | jq -r .job_id)
echo "Job: $JOB"
until [ "$(curl -s https://api.mapsharvest.com/jobs/$JOB -H 'Authorization: Bearer mh_live_...' | jq -r .status)" = "completed" ]; do
sleep 5
done
curl -s "https://api.mapsharvest.com/jobs/$JOB/download?format=csv" \
-H "Authorization: Bearer mh_live_..." -o leads.csv
echo "Done — leads.csv ready"Local Worker (CLI)
EnterpriseThe MapsHarvest CLI lets you run Google Maps scrapes directly on your own machine using your own IP address and Chrome browser — no cloud worker required. This gives you:
- Your own residential / datacenter IP — bypasses proxy detection
- Zero queue time — scrapes start immediately
- Results appear in your dashboard automatically after upload
- Credits deducted only on successful completion
Requires Python 3.9+ and pip. One-time Playwright browser install needed.
# 1. Install the CLI
pip install mapsharvest
# 2. Install Playwright's Chromium browser (one-time)
playwright install chromium
# 3. Save your Enterprise API key
mapsharvest config --api-key mh_live_your_key_here# Scrape "roofers" across all cities in Florida, max 60 results per city
mapsharvest scrape "roofers" --states Florida
# Scrape specific cities with filters
mapsharvest scrape "dentists" \
--states "Texas,Florida" \
--cities "Miami,Houston,Dallas" \
--max-results 100 \
--min-rating 4.0 \
--require-website \
--output leads.csv
# Show the browser (useful for debugging)
mapsharvest scrape "plumbers" --states California --no-headless| Flag | Default | Description |
|---|---|---|
--states / -s | (required) | Comma-separated US state names or abbreviations |
--cities / -c | all cities | Comma-separated city names to restrict the scrape |
--max-results / -n | 60 | Max leads per city (up to 120) |
--output / -o | ./results/<job_id>.csv | Where to save the CSV |
--name | Human-readable label shown in the dashboard | |
--campaign-id | Link this job to an existing campaign UUID | |
--preset | core | Field preset: core · cold_caller · email_outreach · full |
--min-rating | Skip businesses rated below this (e.g. 4.0) | |
--min-reviews | Skip businesses with fewer reviews than this | |
--max-reviews | Skip businesses with more reviews than this | |
--require-website | off | Skip businesses with no website |
--require-phone | off | Skip businesses with no phone |
--headless / --no-headless | --headless | Show or hide the Chrome window |
# View your API key and config
mapsharvest config
# List your 20 most recent jobs
mapsharvest jobs list
# Download a past job as CSV / XLSX / JSON
mapsharvest jobs download <job_id>
mapsharvest jobs download <job_id> --fmt xlsx --output leads.xlsx- Register — the CLI calls
POST /jobs/local-startwith your query and states. The backend validates your Enterprise plan and returns ajob_idplus the list of cities to scrape. - Scrape — Playwright launches on your machine, navigates Google Maps, and writes results to a local CSV. Progress is sent to
PATCH /jobs/{id}/progressafter each city so your dashboard updates live. - Upload — when scraping finishes the CLI uploads the CSV to
POST /jobs/{id}/local-complete. The backend stores it in Supabase Storage and deducts credits (1 per row). - View results — the job appears in your MapsHarvest dashboard exactly like any server-side scrape. Download in CSV, XLSX, or JSON at any time.
| Problem | Fix |
|---|---|
No API key configured | Run: mapsharvest config --api-key mh_live_... |
403 Local worker is an Enterprise feature | Upgrade your plan at mapsharvest.com/pricing |
playwright: command not found | Run: pip install playwright && playwright install chromium |
0 results for a city | Google Maps may have rate-limited the IP. Try --no-headless to debug or wait a few minutes. |
Upload fails but CSV is saved | Your local file is at ./results/<job_id>.csv — you can re-upload or import it manually. |