API
Last updated: September 2, 2026
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.
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.
Give either states or cities, not both.
Region names must be the full name for the chosen country. "Texas"
works; "TX" is rejected. Abbreviations are deliberately not
expanded, because the underlying postal tables 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.
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}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, so a single
job can finish even if it takes you past the line; the next submission is what
gets rejected.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 no provider calls are made.
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, ...}It is worth wiring into a deploy check. If your integration would be rejected, you find out without burning quota.
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}'202.200 with Idempotent-Replay: true,
so the status alone tells you whether you created a second scrape.409
idempotency_key_reuse. Use a new key for a new request.| 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 | Currently a no-op and off by default. Leave it unset. |
dry_run | bool | Validate and return the plan without creating a job. |
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.
Create a free MapsData account and get 500 Google Maps leads with emails every month. No card needed.
Get 500 free leads a month