Evascrape API
Turn a description of an audience into a list of businesses or people with verified email addresses. Built for scripts and for AI agents: every response is JSON, and a job is four calls from start to finish.
Get a key
Create an account, then open app.evascrape.com/account/api-keys and create a key. It is shown once. Send it on every request:
Authorization: Bearer evs_live_...
New accounts get $2.00 in credit, which is enough for about 160 Google Maps records.
Quickstart: 200 dentists in Austin
Google Maps is the fastest starting point. Give a search term and a place.
curl
curl -X POST https://app.evascrape.com/api/v1/jobs \
-H "Authorization: Bearer $EVASCRAPE_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "google_maps",
"list_name": "austin-dentists",
"total_records": 200,
"search_terms": "dentist",
"location": "Austin, Texas"
}'
# -> {"job":{"id":1421,"status":"queued", ...}}
curl https://app.evascrape.com/api/v1/jobs/1421 \
-H "Authorization: Bearer $EVASCRAPE_KEY"
curl "https://app.evascrape.com/api/v1/jobs/1421/results" \
-H "Authorization: Bearer $EVASCRAPE_KEY"
Node
const BASE = 'https://app.evascrape.com/api/v1';
const headers = {
Authorization: `Bearer ${process.env.EVASCRAPE_KEY}`,
'Content-Type': 'application/json',
};
const start = await fetch(`${BASE}/jobs`, {
method: 'POST',
headers,
body: JSON.stringify({
source: 'google_maps',
list_name: 'austin-dentists',
total_records: 200,
search_terms: 'dentist',
location: 'Austin, Texas',
}),
});
const { job } = await start.json();
let state = job;
while (state.status === 'queued' || state.status === 'running') {
await new Promise((r) => setTimeout(r, 20000));
const poll = await fetch(`${BASE}/jobs/${job.id}`, { headers });
({ job: state } = await poll.json());
}
const res = await fetch(`${BASE}/jobs/${job.id}/results`, { headers });
const { records } = await res.json();
console.log(records.length, records[0]);
Python
import os, time, requests
BASE = "https://app.evascrape.com/api/v1"
headers = {"Authorization": f"Bearer {os.environ['EVASCRAPE_KEY']}"}
job = requests.post(f"{BASE}/jobs", headers=headers, json={
"source": "google_maps",
"list_name": "austin-dentists",
"total_records": 200,
"search_terms": "dentist",
"location": "Austin, Texas",
}).json()["job"]
while job["status"] in ("queued", "running"):
time.sleep(20)
job = requests.get(f"{BASE}/jobs/{job['id']}", headers=headers).json()["job"]
data = requests.get(f"{BASE}/jobs/{job['id']}/results", headers=headers).json()
print(data["total"], data["records"][0])
Sources
| source | What you send | Price per 1,000 |
|---|---|---|
google_maps | search_terms and location | $12 |
apollo | apollo_url, a people search URL from Apollo.io | $9 |
linkedin | Filters: job title, location, company, revenue | $9 |
Email verification is an optional add-on at $5 per 1,000, set
"email_verification": true. Records without an email address are
billed at a reduced rate. LinkedIn takes filters, not a search URL.
Endpoints
| Method and path | What it does |
|---|---|
POST /api/v1/quote | Price a job without starting it |
POST /api/v1/jobs | Start an extraction, charges your balance |
GET /api/v1/jobs/{id} | Job status |
GET /api/v1/jobs/{id}/results | Records as JSON, or ?format=csv |
GET /api/v1/jobs | Recent jobs |
GET /api/v1/balance | Credit balance |
GET /api/v1/me | Check that a key works |
Full machine-readable contract: openapi.json.
Billing
You are charged when the job starts, at the requested record count. When the run finishes we
refund whatever we could not deliver, so asking for more than exists costs nothing extra.
credits_charged and credits_refunded
on the job object always add up to what was reserved.
POST /api/v1/quote first if you are spending on behalf of
someone else. It costs nothing and returns the exact reservation amount.
Webhooks
Pass callback_url when starting a job and we POST the finished job
object to it, so you do not have to poll. The URL must be https and public. The body is signed:
X-Evascrape-Event: job.succeeded X-Evascrape-Signature: sha256=<hex HMAC of the raw body>
Verify with the shared secret from your account, then read the same job object the status endpoint returns.
Errors
Every error is JSON with a stable code:
{ "error": { "code": "insufficient_credits", "message": "Your credit is insufficient!" } }
| Code | Meaning |
|---|---|
invalid_api_key | Key is wrong or revoked |
invalid_request | A field is missing or out of range |
insufficient_credits | Top up at app.evascrape.com/account/payment |
not_ready | The job has not finished yet |
rate_limited | 120 requests per minute, 20 job starts per minute |
More on the API
- Google Maps scraper API, the fields it returns and how billing works
- Lead generation API for AI agents, the polling loop and guard rails
- Outscraper alternative · Apify alternative · SerpApi alternative
Limits
- 100 records minimum per job, 50,000 maximum, in multiples of 100.
- 120 requests per minute per key; 20 job starts per minute.
- JSON results are paged:
limitup to 10,000 andoffset. Very large sets must be read withformat=csv. - Only publicly available business data is collected.