API
Submitting a scrape through the API
By the MapsData team · Last updated
A scrape needs two things: a category and a place. Both are validated against fixed lists, and both reject anything they do not recognise rather than guessing. That is deliberate. A silently misread region produces an empty scrape that looks like a product failure, so the API would rather tell you up front.
The category must be a real Google Maps category
query is matched against Google's own category taxonomy. Free
text like "plumbing companies near me" is rejected. Search the
list first:
GET /api/v1/categories?q=plumb
{"data": [{"name": "Plumber"}, {"name": "Plumbing supply store"}]}Or resolve one term directly, which also gives you the exact casing to submit:
GET /api/v1/categories/validate?name=plumber
{"valid": true, "canonical": "Plumber", "suggestions": []}If a submission is rejected, the error carries suggestions so
you can recover in the same round trip. Note that suggestions are substring
matches, not spelling correction: plumb finds Plumber, but a
genuine typo may return nothing.
You can pass up to three comma-separated categories in one job. They run alongside each other and share the job's row budget.
Places
Give either states or cities, not both.
Regions
Region names must be the full name for the chosen country. "Texas"
works; "TX" is rejected. Abbreviations are deliberately not
expanded, because region lookups key on the full name, and
quietly accepting an abbreviation once produced jobs that finished in seconds
with zero leads and no error to explain why.
GET /api/v1/regions?country=us
{"country": "us", "country_name": "United States",
"regions": ["Alabama", "Alaska", ...]}Supported countries are us, au, uk
and ca. Submit region strings exactly as that endpoint returns
them.
Named cities
Pass cities as objects with a city and its full region name.
Up to fifty per job. Every one is re-resolved against our city index, so a city
that does not exist is rejected rather than dropped:
{"query": "Plumber",
"cities": [{"city": "Austin", "state": "Texas"}],
"total_limit": 500}How big a scrape can be
total_limit is the number of rows to scrape. It is capped by
your plan's per-scrape ceiling, which you can read from
GET /api/v1/usage along with the allowance left this cycle.
total_limit counts rows scraped. Your monthly allowance is spent
in rows that carry a deliverable email, which is a fraction of them. A job runs
to its requested row count and bills the email yield afterwards.Over the API, total_limit is additionally capped at the leads
you have left this cycle, so a scrape started from code can never overshoot
your allowance. Ask for 50,000 rows with 200 leads remaining and you get a
200 row job, not a rejection and not an overshoot. Because the cap is applied
in rows against a remainder counted in emails, it is conservative: at a typical
email yield you will collect fewer leads than the remainder suggests, and can
simply submit again.
The 202 response carries the total_limit the job actually got,
so compare it with what you sent. A dry run reports both explicitly, as
requested_total_limit and clamped_to_allowance. Read
leads_remaining from GET /api/v1/usage to size a
request before you send it.
Validate without spending anything
Send "dry_run": true and the request runs every gate, including
the category check, the region check, city resolution and the quota check, then
stops before creating a job. Nothing is queued and nothing is spent.
POST /api/v1/jobs {"query": "Plumber", "states": ["Texas"],
"total_limit": 500, "dry_run": true}
{"would_create": true, "query_terms": ["Plumber"], "states": ["Texas"],
"scrape_mode": "zips_by_population", "total_limit": 500,
"requested_total_limit": 500, "clamped_to_allowance": false, ...}It is worth wiring into a deploy check. If your integration would be rejected, you find out without burning quota.
Safe retries
Send an Idempotency-Key header on a submission and a retry with
the same key and the same body returns the original job rather than creating a
second one.
curl -X POST https://mapsdata.ai/api/v1/jobs \
-H "Authorization: Bearer $MAPSDATA_KEY" \
-H "Idempotency-Key: 4f9a2b60-order-1182" \
-H "Content-Type: application/json" \
-d '{"query": "Plumber", "states": ["Texas"], "total_limit": 500}'- A fresh submission returns
202. - A replay returns
200withIdempotent-Replay: true, so the status alone tells you whether you created a second scrape. - Reusing a key with a different body is
409 idempotency_key_reuse. Use a new key for a new request. - A rejected submission releases its key, so you can fix the body and resend with the same one.
- Keys are remembered for 24 hours.
Every field
| Field | Type | Notes |
|---|---|---|
query | string | Required. One to three comma-separated canonical categories. |
total_limit | int | Required. Rows to scrape, capped by your plan ceiling. |
country | string | One of us, au, uk, ca. Defaults to us. |
states | string[] | Full region names. Omit when using cities. |
cities | object[] | Up to 50 {city, state} pairs. |
list_name | string | Label for the list. Defaults to the query. |
client_id | string | Only for multi-client workspaces. Defaults to your Main client. See GET /api/v1/clients. |
min_reviews | int | Skip businesses with fewer reviews. |
max_reviews | int | Skip businesses with more reviews. |
max_emails_per_company | int | Cap emails kept per business. Omit for no cap. Must be 1 or more. |
include_variants | bool | Default true. Also searches up to two closely related categories, which usually finds more unique businesses. Single-category submissions only. |
dry_run | bool | Validate and return the plan without creating a job. |
How many scrapes you can have waiting
By default a workspace can have one scrape queued at a time, on top of
whatever is already running. A script that fires twenty submissions in a loop
will see most of them rejected with 409 queue_depth_exceeded.
Submit one, watch it, then submit the next. If your workflow genuinely needs
more depth, talk to us and we will raise it on your workspace.
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