Skip to content

API

Google Maps scraper API

By the MapsData team · Last updated

The MapsData API lets you submit a scrape, track it, and download the result from your own code. It is a small, deliberately boring JSON API: nine endpoints, bearer authentication, and the same validation and quota rules the dashboard uses. Anything you can start from the search form you can start from a script.

Included on every paid plan. API keys are created in Settings once your workspace is on Starter, Pro, Scale or a custom plan. Every scrape counts against the same monthly lead allowance as the dashboard, so there is no separate API metering to reason about.

Quickstart

Three requests, start to finish. Create a key in Settings, then export it:

export MAPSDATA_KEY="mdk_live_..."

1. Check the category is scrapable

A scrape's query has to be a real Google Maps category, not free text. This is the single most common reason a submission is rejected, so it is worth one call up front.

curl -s https://mapsdata.ai/api/v1/categories/validate?name=plumber \ -H "Authorization: Bearer $MAPSDATA_KEY" {"valid": true, "canonical": "Plumber", "suggestions": []}

Submit the canonical spelling exactly as returned.

2. Start the scrape

curl -s -X POST https://mapsdata.ai/api/v1/jobs \ -H "Authorization: Bearer $MAPSDATA_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Plumber", "states": ["Texas"], "total_limit": 500}' {"id": "8f2c...", "status": "queued", "progress": 0, ...}

You get 202 Accepted and a job id straight away. The scrape itself runs on our workers and takes anywhere from a few minutes to several hours. Add "dry_run": true to run every validation and quota check and see the resolved plan without creating a job or spending anything.

3. Poll, then download

curl -s https://mapsdata.ai/api/v1/jobs/8f2c... \ -H "Authorization: Bearer $MAPSDATA_KEY" {"id": "8f2c...", "status": "completed", "progress": 100, "lead_count": 480, "email_count": 151, "downloads": {"csv": true, "xlsx": true, ...}}

Once status is completed, ask for a link. It is a short-lived signed URL, returned as JSON rather than a redirect:

curl -s "https://mapsdata.ai/api/v1/jobs/8f2c.../download?format=csv" \ -H "Authorization: Bearer $MAPSDATA_KEY" {"url": "https://...", "expires_at": "...", "filename": "Plumber.csv"}

The link expires in fifteen minutes. Request a new one rather than storing it. We also email you when a scrape finishes, so polling is a convenience, not the only way to find out.

The same flow in Python and JavaScript

Python, with requests:

import os, time, requests API = "https://mapsdata.ai/api/v1" H = {"Authorization": f"Bearer {os.environ['MAPSDATA_KEY']}"} cat = requests.get(f"{API}/categories/validate", params={"name": "plumber"}, headers=H).json() job = requests.post(f"{API}/jobs", headers=H, json={ "query": cat["canonical"], "states": ["Texas"], "total_limit": 500, }).json() while True: status = requests.get(f"{API}/jobs/{job['id']}", headers=H).json() if status["status"] in ("completed", "failed", "cancelled"): break time.sleep(60) link = requests.get(f"{API}/jobs/{job['id']}/download", params={"format": "csv"}, headers=H).json() print(link["url"])

JavaScript (Node 18 or later, built-in fetch):

const API = "https://mapsdata.ai/api/v1"; const H = { Authorization: `Bearer ${process.env.MAPSDATA_KEY}`, "Content-Type": "application/json" }; const cat = await (await fetch(`${API}/categories/validate?name=plumber`, { headers: H })).json(); const job = await (await fetch(`${API}/jobs`, { method: "POST", headers: H, body: JSON.stringify({ query: cat.canonical, states: ["Texas"], total_limit: 500 }), })).json(); let status; do { await new Promise((r) => setTimeout(r, 60_000)); status = await (await fetch(`${API}/jobs/${job.id}`, { headers: H })).json(); } while (!["completed", "failed", "cancelled"].includes(status.status)); const link = await (await fetch(`${API}/jobs/${job.id}/download?format=csv`, { headers: H })).json(); console.log(link.url);

Poll every minute or two at most; polling and lifecycle explains why, and errors and rate limits covers retries.

What the file contains

The CSV has one row per business email with up to 17 columns: name, email, phone, website, domain, address, city, region, zip code, rating, review count, categories, business status, claimed, opening hours, a Google Maps link and the category you searched. Every column is described in the data fields guide.

Endpoints

MethodPathWhat it does
GET/api/v1/categoriesSearch the Google Maps category list
GET/api/v1/categories/validateResolve one term to its canonical spelling
GET/api/v1/regionsValid region names for a country
GET/api/v1/clientsClient labels this key may file scrapes under
GET/api/v1/usageThis cycle's allowance and per-scrape ceiling
POST/api/v1/jobsSubmit a scrape
GET/api/v1/jobsList scrapes, newest first
GET/api/v1/jobs/{id}One scrape's current state
POST/api/v1/jobs/{id}/cancelCancel a queued or running scrape
GET/api/v1/jobs/{id}/downloadA short-lived signed download link

The machine-readable contract is at https://mapsdata.ai/api/v1/openapi.json. It is an OpenAPI 3.1 document covering exactly these paths and nothing else, so you can generate a client from it.

Conventions

  • Base URL is https://mapsdata.ai/api/v1. Everything is HTTPS.
  • Authentication is Authorization: Bearer <key> on every request. See authentication.
  • Bodies are JSON. Unknown fields are rejected rather than ignored, so a typo in a field name is a loud error instead of a silent no-op.
  • Errors all share one envelope with a stable code. See errors and rate limits.
  • Times are ISO 8601 with an offset.

Where to go next

Build on MapsData

The REST API and the MCP server are included on every paid plan, with the same allowance as the app. Subscribe or start a trial, then create a key in Settings.

See plans and API access