# REST API v1 reference · Leadraft

URL: https://leadraft.co/docs/api
Summary: Leadraft's REST API: start searches, follow them, list leads with verified emails, and check credits and usage. Endpoints, parameters, errors and examples.

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

<table><tbody><tr><td class="w-[1%] whitespace-nowrap"><span class="inline-flex h-6 items-center rounded-md px-2 font-mono text-[12px] font-semibold bg-brand-soft text-brand-deep">POST</span></td><td class="whitespace-nowrap"><a href="#create-search"><code>/searches</code></a></td><td>Start a search</td></tr><tr><td class="w-[1%] whitespace-nowrap"><span class="inline-flex h-6 items-center rounded-md px-2 font-mono text-[12px] font-semibold bg-secondary-soft text-secondary">GET</span></td><td class="whitespace-nowrap"><a href="#get-search"><code>/searches/{group}</code></a></td><td>Get a search</td></tr><tr><td class="w-[1%] whitespace-nowrap"><span class="inline-flex h-6 items-center rounded-md px-2 font-mono text-[12px] font-semibold bg-secondary-soft text-secondary">GET</span></td><td class="whitespace-nowrap"><a href="#list-searches"><code>/searches</code></a></td><td>List searches</td></tr><tr><td class="w-[1%] whitespace-nowrap"><span class="inline-flex h-6 items-center rounded-md px-2 font-mono text-[12px] font-semibold bg-secondary-soft text-secondary">GET</span></td><td class="whitespace-nowrap"><a href="#list-leads"><code>/leads</code></a></td><td>List leads</td></tr><tr><td class="w-[1%] whitespace-nowrap"><span class="inline-flex h-6 items-center rounded-md px-2 font-mono text-[12px] font-semibold bg-secondary-soft text-secondary">GET</span></td><td class="whitespace-nowrap"><a href="#get-lead"><code>/leads/{id}</code></a></td><td>Get a lead</td></tr><tr><td class="w-[1%] whitespace-nowrap"><span class="inline-flex h-6 items-center rounded-md px-2 font-mono text-[12px] font-semibold bg-secondary-soft text-secondary">GET</span></td><td class="whitespace-nowrap"><a href="#credits"><code>/credits</code></a></td><td>Credits</td></tr><tr><td class="w-[1%] whitespace-nowrap"><span class="inline-flex h-6 items-center rounded-md px-2 font-mono text-[12px] font-semibold bg-secondary-soft text-secondary">GET</span></td><td class="whitespace-nowrap"><a href="#usage"><code>/usage</code></a></td><td>Usage by day</td></tr></tbody></table>

## 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" }
```

| 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/searches`needs 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 |
| --- | --- | --- | --- |
| `queries`required | 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 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 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 |
| --- | --- | --- | --- |
| `group`required | path | string | The `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.

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

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id`required | 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.

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.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `days` | query | integer (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
}
```

## Related pages

-   [QuickstartThe endpoints in order, with code](https://leadraft.co/docs/quickstart)
-   [MCP server](https://leadraft.co/docs/mcp)
-   [Webhooks](https://leadraft.co/docs/webhooks)

Last built: 2026-11-09
