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/searchesStart a search
GET/searches/{group}Get a search
GET/searchesList searches
GET/leadsList leads
GET/leads/{id}Get a lead
GET/creditsCredits
GET/usageUsage 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.

Header
Authorization: Bearer lg_your_key

A 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

Base URL
https://app.leadraft.co/api/v1

Rate 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.

Example
HTTP/1.1 403 Forbidden

{ "detail": "This API key is read-only.", "code": "read_only_key" }
StatusMeaning
401Missing or invalid API key.
402No business credits left this period, or payment is overdue (starting searches only).
403The key is read-only (code `read_only_key`), or the organization is suspended.
404Not found, or not yours.
422A parameter is missing or invalid. `detail` says which.
429Over 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

ValueIn the appMeaning
validVerifiedThe mail server confirmed the mailbox.
catch_allAccept-allThe domain accepts any address: a maybe, never a yes.
riskyRiskyThe server answered ambiguously.
unknownUncheckedNot verified yet, or the server couldn't be reached.

Invalid, suppressed and opted-out addresses are never returned.

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.

NameInTypeDescription
queriesrequiredbodystring[]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.
namebodystringWhat to call the search. Defaults to its first line.
email_requiredbodybooleanOnly count businesses that end up with an email. Default true.
scopebody"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_kmbodynumberThe ring around the location for near (0–500 km). Default 25.
regionbodystring (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_largebodybooleanRequired 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.
freshbodybooleanSearch Google again even where saved results exist. Same cost, slower. Default false.
web_searchbodybooleanLook up emails by web search where a business's site gave none (Pro and Scale plans). Default false.
Idempotency-KeyheaderstringSend 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 the search_group filter on /leads.
  • Returns 402 when the organization has no business credits left (or payment is overdue).
Request
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" }'
Response
{
  "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/api/v1/searches/{group}

A search's progress, line by line: status, searches done of the total, and businesses and emails found so far.

NameInTypeDescription
grouprequiredpathstringThe group returned by POST /searches.
  • status is running, finishing (searching is done, emails are still being found and checked), paused or completed.
  • Poll every 10–30 seconds. Businesses usually start arriving within minutes.
Request
curl "https://app.leadraft.co/api/v1/searches/g_7f3a9c2e" \
  -H "Authorization: Bearer lg_your_key"
Response
{
  "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.

Request
curl "https://app.leadraft.co/api/v1/searches" \
  -H "Authorization: Bearer lg_your_key"
Response
[
  {
    "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.

NameInTypeDescription
search_groupquerystringOnly leads from this search.
has_emailquerybooleanOnly businesses with (or without) a sendable email.
gradequerystring (repeatable)Email grades to include: valid, catch_all, risky, unknown. Invalid addresses are never returned.
qquerystringFree text over name and address.
keywordquerystringThe search keyword that found the business.
categoryquerystring (repeatable)Google Maps category label. exclude_category does the opposite.
countryquerystring (2 letters)Country code, e.g. US.
admin1 / locality / postcodequerystring (repeatable)State or region, city, postcode.
min_rating / max_ratingquerynumber (0–5)Google rating range.
min_review_count / max_review_countqueryintegerReview count range.
has_phone / has_website / has_socialquerybooleanContact details present.
found_sincequery"30d" or dateFirst found within a rolling window or since an ISO date.
cursorqueryintegernext from the previous page.
limitqueryinteger (1–500)Page size. Default 100.
  • Keyset-paged: pass next back as cursor. next is 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's emails_withheld counts them. They come back once you have credits.
Request
curl "https://app.leadraft.co/api/v1/leads?search_group=g_7f3a9c2e&has_email=true&limit=100" \
  -H "Authorization: Bearer lg_your_key"
Response
{
  "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.

NameInTypeDescription
idrequiredpathintegerThe lead's id from /leads.
  • 404 for any business your searches don't count.
  • Emails are charged the same way as on /leads.
Request
curl "https://app.leadraft.co/api/v1/leads/48213" \
  -H "Authorization: Bearer lg_your_key"
Response
{
  "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.

Request
curl "https://app.leadraft.co/api/v1/credits" \
  -H "Authorization: Bearer lg_your_key"
Response
{
  "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.

NameInTypeDescription
daysqueryinteger (1–90)How many days back, including today (UTC). Default 30.
Request
curl "https://app.leadraft.co/api/v1/usage?days=30" \
  -H "Authorization: Bearer lg_your_key"
Response
{
  "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
}