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

sourceWhat you sendPrice per 1,000
google_mapssearch_terms and location$12
apolloapollo_url, a people search URL from Apollo.io$9
linkedinFilters: 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 pathWhat it does
POST /api/v1/quotePrice a job without starting it
POST /api/v1/jobsStart an extraction, charges your balance
GET /api/v1/jobs/{id}Job status
GET /api/v1/jobs/{id}/resultsRecords as JSON, or ?format=csv
GET /api/v1/jobsRecent jobs
GET /api/v1/balanceCredit balance
GET /api/v1/meCheck 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.

Call 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!" } }
CodeMeaning
invalid_api_keyKey is wrong or revoked
invalid_requestA field is missing or out of range
insufficient_creditsTop up at app.evascrape.com/account/payment
not_readyThe job has not finished yet
rate_limited120 requests per minute, 20 job starts per minute

More on the API

Limits