Lead generation API for AI agents
An assistant asked to "find 200 dentists in Austin with their emails" needs four things: a way to check the price, a way to start the work, a way to know when it finished, and structured records at the end. That is the whole surface here.
Get an API keyThe loop
Four calls, no SDK required. This is the shape an agent should implement:
1. POST /api/v1/quote ask what it costs (free, changes nothing)
2. POST /api/v1/jobs start it (charges the balance, returns 202)
3. GET /api/v1/jobs/{id} poll every 20-30 seconds (until status is terminal)
4. GET /api/v1/jobs/{id}/results read records (JSON, paged)
Step 1, ask the price first
This matters more for an agent than for a human, because the agent is spending a balance it does not own. The quote endpoint is free and has no side effects. A real response:
POST /api/v1/quote {"source": "google_maps", "total_records": 200}
{
"source": "google_maps",
"total_records": 200,
"email_verification": false,
"cost": { "base": "2.40", "verification": "0.00", "total": "2.40" },
"currency": "USD",
"note": "You are charged up front and refunded for any records we cannot deliver."
}
Present that number to the user before spending it. If the balance is short, the start call
returns 402 insufficient_credits rather than half-doing the work.
Step 2, start the job
POST /api/v1/jobs
{
"source": "google_maps",
"list_name": "austin-dentists",
"total_records": 200,
"search_terms": "dentist",
"location": "Austin, Texas"
}
Three sources exist. Google Maps takes a search term and a place. Apollo.io takes a people search URL the user already built there. LinkedIn takes filters, not a URL: job titles, locations, company domains, keywords, revenue range and more.
Step 3, poll until terminal
Five states, three of them terminal. Real response from a finished job:
GET /api/v1/jobs/90
{
"job": {
"id": 90,
"status": "succeeded",
"source": "google_maps",
"list_name": "austin-dentists",
"requested_records": 200,
"delivered_records": 173,
"records_with_email": 118,
"credits_charged": "2.40",
"credits_refunded": "0.32",
"error": null,
"results": { "available": true, "variants": ["all"], "url": "/api/v1/jobs/90/results" }
}
}
| status | What the agent should do |
|---|---|
queued, running | Wait 20 to 30 seconds and poll again |
succeeded | Read the results |
no_results | Nothing matched. The full amount was refunded. Suggest a broader search rather than retrying the same one |
failed | Read error, report it, do not silently retry: the money was already refunded |
If you would rather not poll, pass callback_url when starting the job.
The finished job object is posted there, signed with HMAC-SHA256.
Step 4, read the records
GET /api/v1/jobs/90/results?limit=1000&offset=0
{ "total": 173, "offset": 0, "limit": 1000, "has_more": false,
"fields": ["Place_Name", "Email", "Phone", "..."],
"records": [ { "Place_Name": "...", "Email": "...", "Phone": "..." } ] }
Guard rails worth implementing
- Quote before spending. Always, when acting for someone else. It is free.
- Treat the requested count as a ceiling. Asking for 5,000 when 300 exist costs the price of 300, so over-estimating is safe and under-estimating is not.
- Check the balance first with
GET /api/v1/balancefor long-running plans. - Respect the rate limits: 120 requests per minute, 20 job starts per minute. A polling loop that fires every second will hit the ceiling for no benefit.
- Do not retry a failed job automatically. Failures already refund. A blind retry doubles the spend and usually reproduces the same failure.
- Report what came back, not what was asked for. The difference between
requested_recordsanddelivered_recordsis the honest answer to give the user.
What it costs
| Source | Per 1,000 records |
|---|---|
| Google Maps | $12 |
| Apollo.io | $9 |
| LinkedIn and Sales Navigator | $9 |
| Real-time email verification, optional | +$5 |
Records delivered without an email address are billed at a reduced rate. Prepaid balance in USD, no subscription. A confirmed account starts with $2.00 of credit, which is enough to run a first job end to end before deciding anything.
Errors an agent will actually hit
401 {"error":{"code":"invalid_api_key","message":"This API key is not valid, or it was revoked."}}
400 {"error":{"code":"invalid_request","message":"source must be one of: google_maps, apollo, linkedin."}}
400 {"error":{"code":"invalid_request","message":"Search terms and location are required!"}}
402 {"error":{"code":"insufficient_credits","message":"Your credit is insufficient!"}}
429 {"error":{"code":"rate_limited","message":"Too many requests. Try again in a minute."}}
Every error is JSON with a stable code and a message written to be
shown to a person. Branch on the code, show the message.