API
API polling and job lifecycle
By the MapsData team · Last updated
A scrape is a background job, not a request you wait on. Submitting returns immediately with a job id, and the work runs in the background on our side. How long that takes depends far more on how much of the target area we have seen recently than on the size of the job, so plan for a range rather than a number.
States
queued -> running -> completed
-> failed
-> cancelling -> cancelled| Status | Meaning |
|---|---|
queued | Accepted, waiting for a free worker. |
running | Being scraped. progress and lead_count move. |
completed | Finished. Files are ready to download. |
failed | Something went wrong. error_message says what. Partial results are often still exported and are not billed. |
cancelling | Cancel requested, worker is winding down. |
cancelled | Stopped. Leads already delivered are still billed. |
How long it takes
Minutes to several hours. The dominant factor is how many business websites in the target area we have to visit for the first time to extract an email; areas we have covered recently finish far faster than fresh ones. A job's own row count matters much less than that.
This means you should not build a timeout that assumes a small job is a fast job. Watch the status, not the clock.
Polling, sensibly
Poll at an interval measured in minutes, not seconds. Once a minute is
plenty for a job that will run for an hour, and the rate limit will stop a
tight loop anyway. A reasonable pattern is to start at roughly
max(60, total_limit / 200) seconds and back off from there.
GET /api/v1/jobs/8f2c...
{"id": "8f2c...", "status": "running", "progress": 42,
"lead_count": 210, "email_count": 0,
"stop_reason": null, "downloads": {...}}Reading the numbers
progressis a percentage, and it is an estimate. It can sit still while a slow area is worked through.lead_countis rows scraped so far, and moves during the run.email_countis rows carrying a deliverable email. It is set when the job settles, and it is the figure your allowance is billed on.stop_reasonsays why the scrape stopped. It is set on essentially every completed scrape, so treat it as informational rather than as a problem signal, and compare it against specific values rather than testing whether it is present.
| stop_reason | What happened | Short result? |
|---|---|---|
limit_reached | It scraped the total_limit you asked for. The ordinary outcome. | No |
coverage_exhausted | It searched every area you selected and that was all there was. Re-running the same search returns the same count. Widen the region, or add cities. | Expected |
diminishing_returns | Further calls would mostly have repeated businesses already found, so it stopped. A healthy outcome, not a failure. | Expected |
barren_query | The densest areas returned almost nothing, so there was no market to keep walking toward. Usually means the category is genuinely rare in that region. | Expected |
user_cancel | Someone cancelled it. Leads already delivered are still billed. | Yes |
stall | It stopped making progress and was ended rather than left hanging. Retrying is reasonable. | Yes |
memory_checkpoint | A very large scrape was checkpointed and recycled mid-run. Internal housekeeping. | Sometimes |
null | No reason was recorded. Read it alongside status: a cancelled or failed run can stop before one is set, and older scrapes predate the field. | Sometimes |
The last three are rare. stall and memory_checkpoint
exist so that an unusual ending is still explained rather than silent, and
neither had occurred on a customer scrape as of October 2026.
stop_reason before treating a scrape as having
underdelivered, and before retrying it.Listing scrapes
Newest first, cursor paginated. Pass the previous page's
next_cursor to continue. Do not build page numbers; the cursor is
stable even when new jobs arrive mid-pagination.
GET /api/v1/jobs?limit=25
GET /api/v1/jobs?limit=25&cursor=<next_cursor>
GET /api/v1/jobs?status=runningDownloading
Check the downloads object on the job to see which files exist,
then ask for a link. The flags and the download endpoint always agree: if a
flag is false, that file genuinely does not exist and asking for it returns
404 rather than quietly handing you a different one.
GET /api/v1/jobs/8f2c.../download?format=csv&scope=emails
{"url": "https://...", "expires_at": "2026-09-02T22:45:00Z",
"format": "csv", "scope": "emails", "filename": "Plumber.csv"}| Parameter | Values | Meaning |
|---|---|---|
format | csv, xlsx | File type. Defaults to csv. |
scope | emails | Rows with a deliverable email. The default, and what you are billed on. |
all | Every scraped row, email or not. | |
filtered | Output of a filter run, if one exists. |
Links expire after fifteen minutes. Fetch the file promptly, and request a
new link rather than caching the URL. We return the link as JSON rather than
redirecting to it, because a redirect to storage makes some HTTP clients resend
your Authorization header to a third-party host.
Cancelling
POST /api/v1/jobs/8f2c.../cancelA queued scrape stops for free. Cancelling a running scrape still
bills the leads already delivered, the same as in the dashboard, so it
is not a way to undo a submission. A job that has already finished returns
409 job_not_cancellable.
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