API

Submitting a scrape

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.

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 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.

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.

Rows and leads are different units. 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.

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 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.

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 200 with Idempotent-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

FieldTypeNotes
querystringRequired. One to three comma-separated canonical categories.
total_limitintRequired. Rows to scrape, capped by your plan ceiling.
countrystringOne of us, au, uk, ca. Defaults to us.
statesstring[]Full region names. Omit when using cities.
citiesobject[]Up to 50 {city, state} pairs.
list_namestringLabel for the list. Defaults to the query.
client_idstringOnly for multi-client workspaces. Defaults to your Main client. See GET /api/v1/clients.
min_reviewsintSkip businesses with fewer reviews.
max_reviewsintSkip businesses with more reviews.
max_emails_per_companyintCap emails kept per business. Omit for no cap. Must be 1 or more.
include_variantsboolCurrently a no-op and off by default. Leave it unset.
dry_runboolValidate 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.

Try it on your own market

Create a free MapsData account and get 500 Google Maps leads with emails every month. No card needed.

Get 500 free leads a month
MapsData © 2026 Hiive Arts Home Pricing Privacy Policy Terms of Service