Docs · Reference · Last updated October 31, 2026
REST API v1
A stable surface for scripts, automation tools and your own code. Breaking changes will come as /api/v2, never to v1.
Overview
| POST | /searches | Start a search |
| GET | /searches/{group} | Get a search |
| GET | /searches | List searches |
| GET | /leads | List leads |
| GET | /leads/{id} | Get a lead |
| GET | /credits | Credits |
| GET | /usage | Usage by day |
Authentication
Every request needs an organization API key as a bearer token. Keys look like lg_1a2b3c4d_…; create and revoke them in the app under Settings → API keys (admins only). Keys are shown once and stored only as a hash.
Authorization: Bearer lg_your_keyA key has every permission a member has, or only read if you chose that scope. Read-only keys get 403 on endpoints that start work.
Base URL
https://app.leadraft.co/api/v1Rate limits
120 requests a minute per key, with bursts of up to 30. Every response carries RateLimit-Limit. Over the limit you get 429 with a Retry-After header in seconds.
Errors
Errors are JSON with a human-readable detail, and sometimes a machine-readable code.
HTTP/1.1 403 Forbidden
{ "detail": "This API key is read-only.", "code": "read_only_key" }| Status | Meaning |
|---|---|
401 | Missing or invalid API key. |
402 | No business credits left this period, or payment is overdue (starting searches only). |
403 | The key is read-only (code `read_only_key`), or the organization is suspended. |
404 | Not found, or not yours. |
422 | A parameter is missing or invalid. `detail` says which. |
429 | Over 120 requests a minute for this key. Wait `Retry-After` seconds. |
Pagination
GET /leads is keyset-paged: each page has next; pass it back as cursor until it's null. Pages don't shift when new leads arrive.
Email grades
| Value | In the app | Meaning |
|---|---|---|
valid | Verified | The mail server confirmed the mailbox. |
catch_all | Accept-all | The domain accepts any address: a maybe, never a yes. |
risky | Risky | The server answered ambiguously. |
unknown | Unchecked | Not verified yet, or the server couldn't be reached. |
Invalid, suppressed and opted-out addresses are never returned.
Start a search
POST/api/v1/searchesneeds a key with write access
Start one or more "keyword in location" searches. Each line runs as its own search; lines that can't run come back in failed with the reason.
| Name | In | Type | Description |
|---|---|---|---|
queriesrequired | body | string[] | Lines like "plumber in Austin, TX" or "hotel near Central Park, NY". "plumber, electrician in Austin" is two searches. Up to 500 lines and 25 different locations per request. |
name | body | string | What to call the search. Defaults to its first line. |
email_required | body | boolean | Only count businesses that end up with an email. Default true. |
scope | body | "all" | "near" | "in" | What to count and charge: everything the search returns, inside the location plus scope_km, or only inside its boundary. Default "all". |
scope_km | body | number | The ring around the location for near (0–500 km). Default 25. |
region | body | string (2 letters) | Country that settles an ambiguous place name, e.g. "GB" for Manchester, England. Omit to take the best match on the map. |
confirm_large | body | boolean | Required for a location over 20,000 km² (a state or country). Without it, those lines come back in failed with code confirm_large. Default false. |
fresh | body | boolean | Search Google again even where saved results exist. Same cost, slower. Default false. |
web_search | body | boolean | Look up emails by web search where a business's site gave none (Pro and Scale plans). Default false. |
Idempotency-Key | header | string | Send a unique value to make a retry return the first answer instead of starting the searches twice. |
- Returns 201. Keep
group: it identifies the search for GET /searches/{group} and thesearch_groupfilter on /leads. - Returns 402 when the organization has no business credits left (or payment is overdue).
curl -X POST "https://app.leadraft.co/api/v1/searches" \
-H "Authorization: Bearer lg_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "queries": ["dentist in Austin, TX", "orthodontist in Austin, TX"], "email_required": true, "scope": "in" }'{
"group": "g_7f3a9c2e",
"lines": [
{
"query": "dentist in Austin, TX",
"keyword": "dentist",
"location": "Austin, TX",
"area_name": "Austin, Texas, United States",
"campaign_id": 912,
"status": "running",
"state_kind": "searching",
"businesses": 0,
"with_email": 0,
"off_topic": 0,
"outside": 0,
"searches_done": 0,
"searches_total": 64
}
],
"failed": []
}Get a search
GET/api/v1/searches/{group}
A search's progress, line by line: status, searches done of the total, and businesses and emails found so far.
| Name | In | Type | Description |
|---|---|---|---|
grouprequired | path | string | The group returned by POST /searches. |
statusisrunning,finishing(searching is done, emails are still being found and checked),pausedorcompleted.- Poll every 10–30 seconds. Businesses usually start arriving within minutes.
curl "https://app.leadraft.co/api/v1/searches/g_7f3a9c2e" \
-H "Authorization: Bearer lg_your_key"{
"group": "g_7f3a9c2e",
"name": "dentist in Austin, TX +1 more",
"created_at": "2026-10-31T09:12:44Z",
"queries": 2,
"status": "running",
"email_required": true,
"businesses": 418,
"with_email": 251,
"searches_done": 97,
"searches_total": 128,
"lines": [
"…one entry per line, as in POST /searches…"
]
}List searches
GET/api/v1/searches
The organization's latest 100 searches, newest first, with status and counts.
No parameters.
curl "https://app.leadraft.co/api/v1/searches" \
-H "Authorization: Bearer lg_your_key"[
{
"group": "g_7f3a9c2e",
"name": "dentist in Austin, TX +1 more",
"created_at": "2026-10-31T09:12:44Z",
"queries": 2,
"status": "completed",
"businesses": 1204,
"with_email": 702
}
]List leads
GET/api/v1/leads
Businesses your searches found and count (inside each search's chosen scope and matching what it searched for), newest first, each with its best sendable email.
| Name | In | Type | Description |
|---|---|---|---|
search_group | query | string | Only leads from this search. |
has_email | query | boolean | Only businesses with (or without) a sendable email. |
grade | query | string (repeatable) | Email grades to include: valid, catch_all, risky, unknown. Invalid addresses are never returned. |
q | query | string | Free text over name and address. |
keyword | query | string | The search keyword that found the business. |
category | query | string (repeatable) | Google Maps category label. exclude_category does the opposite. |
country | query | string (2 letters) | Country code, e.g. US. |
admin1 / locality / postcode | query | string (repeatable) | State or region, city, postcode. |
min_rating / max_rating | query | number (0–5) | Google rating range. |
min_review_count / max_review_count | query | integer | Review count range. |
has_phone / has_website / has_social | query | boolean | Contact details present. |
found_since | query | "30d" or date | First found within a rolling window or since an ISO date. |
cursor | query | integer | next from the previous page. |
limit | query | integer (1–500) | Page size. Default 100. |
- Keyset-paged: pass
nextback ascursor.nextis null on the last page. - Reading a Verified address you haven't been charged for uses one checked-email credit, once. If you're out of credits the address comes back null with
email_withheld: true, and the page'semails_withheldcounts them. They come back once you have credits.
curl "https://app.leadraft.co/api/v1/leads?search_group=g_7f3a9c2e&has_email=true&limit=100" \
-H "Authorization: Bearer lg_your_key"{
"items": [
{
"id": 48213,
"name": "Bright Smile Dental",
"category": "Dentist",
"gcid": "dentist",
"rating": 4.8,
"review_count": 212,
"locality": "Austin",
"admin1": "Texas",
"postal_code": "78704",
"country_code": "US",
"phone_e164": "+15125550142",
"website_domain": "brightsmile.example",
"first_seen": "2026-10-28T14:02:11Z",
"lat": 30.2463,
"lng": -97.7686,
"in_area": true,
"distance_km": 0,
"area_name": "Austin, Texas, United States",
"counted": true,
"email": "hello@brightsmile.example",
"email_grade": "valid",
"email_confidence": 0.96,
"email_withheld": false
}
],
"next": 48190,
"emails_withheld": 0
}Get a lead
GET/api/v1/leads/{id}
One business with its full address, phone, website, hours, every sendable email with grade and source, and which searches found it.
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | The lead's id from /leads. |
- 404 for any business your searches don't count.
- Emails are charged the same way as on /leads.
curl "https://app.leadraft.co/api/v1/leads/48213" \
-H "Authorization: Bearer lg_your_key"{
"id": 48213,
"google_place_id": "ChIJ…",
"name": "Bright Smile Dental",
"category": "Dentist",
"formatted_address": "1200 S Lamar Blvd, Austin, TX 78704",
"phone_e164": "+15125550142",
"website_url": "https://brightsmile.example/",
"hours": "Mon–Fri 8 AM–5 PM",
"maps_url": "https://www.google.com/maps/place/?q=place_id:ChIJ…",
"emails": [
{
"address": "hello@brightsmile.example",
"grade": "valid",
"confidence": 0.96,
"source_type": "website",
"role_type": "generic",
"is_catchall": false,
"verified_at": "2026-10-28T14:09:30Z"
}
],
"emails_withheld": 0,
"found_by": [
{
"campaign_id": 912,
"campaign": "dentist in Austin, TX",
"keyword": "dentist",
"in_area": true,
"distance_km": 0,
"on_topic": true,
"counted": true
}
],
"crawl": {
"status": "ok",
"platform": "wordpress",
"pages_fetched": 4,
"socials": {
"facebook": "https://facebook.com/brightsmile.example"
}
}
}Credits
GET/api/v1/credits
Your plan, this period's allowance, what's left, and whether you can start a search.
No parameters.
curl "https://app.leadraft.co/api/v1/credits" \
-H "Authorization: Bearer lg_your_key"{
"plan": "growth",
"unlimited": false,
"status": "active",
"period_end": "2026-11-30T00:00:00Z",
"allowance": {
"business": 100000,
"checked_email": 5000
},
"balance": {
"business": 81240,
"checked_email": 4210
},
"used": {
"business": 18760,
"checked_email": 790
},
"can_search": true
}Usage by day
GET/api/v1/usage
Businesses and checked emails charged per day, plus search activity.
| Name | In | Type | Description |
|---|---|---|---|
days | query | integer (1–90) | How many days back, including today (UTC). Default 30. |
curl "https://app.leadraft.co/api/v1/usage?days=30" \
-H "Authorization: Bearer lg_your_key"{
"days": [
{
"day": "2026-10-31",
"businesses": 1204,
"checked_emails": 312,
"proxy_searches": 128,
"proxy_bytes": 2969600,
"search_cache_hits": 41,
"web_lookups": 0
}
],
"totals": {
"businesses": 18760,
"checked_emails": 790,
"proxy_searches": 2210,
"proxy_bytes": 51303200,
"search_cache_hits": 640,
"web_lookups": 0,
"proxy_gb": 0.051
},
"cache_share": 0.225
}