API
Last updated: September 2, 2026
A scrape is a background job, not a request you wait on. Submitting returns immediately with a job id, and the work happens on our scraping fleet. 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.
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. |
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.
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": {...}}progress is a percentage, and it is an estimate. It can sit
still while a slow area is worked through.lead_count is rows scraped so far, and moves during the run.email_count is rows carrying a deliverable email. It is set
when the job settles, and it is the figure your allowance is billed on.stop_reason says 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 has occurred on a customer scrape to date.
stop_reason before treating a scrape as having
underdelivered, and before retrying it.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=runningCheck 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.
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.
Create a free MapsData account and get 500 Google Maps leads with emails every month. No card needed.
Get 500 free leads a month