LLeadGen.tools API v1
API v1 · REST · JSON

Find customers and email them — from your code or your AI agent.

The LeadGen.tools API finds real businesses, their contacts and decision makers, verifies emails, and runs cold email campaigns with automatic follow-ups and AI reply detection. It is the same engine as the web app, billed from the same credit balance.

Base URL

https://mail.leadgen.tools/v2/api/v1/

Every endpoint is a .php file under this URL, for example https://mail.leadgen.tools/v2/api/v1/companies.php. Use HTTPS.

Requests

  • GET endpoints take their parameters in the query string.
  • POST / PUT endpoints take a JSON body with Content-Type: application/json.
  • companies.php and people.php accept both: query string with GET, or a JSON body with POST.
  • api_key always goes in the query string, on every call — also on POST and PUT.

Responses

Every response is JSON (Content-Type: application/json; charset=utf-8) with the same envelope:

Success
{
  "status": "success",
  "credits_used": 4,
  "data": { }
}
Error 402
{
  "status": "error",
  "code": "insufficient_credits",
  "message": "You have no credits remaining. Current balance: 0"
}

credits_used is what that call took from your balance. 0 means nothing billable was delivered, the answer came from the cache, or you had already paid for those items. Errors carry a machine-readable code and a readable message — see Errors.

All endpoints

EndpointMethodWhat it doesCredits
companies.phpGET · POSTBusinesses for a niche + city, optionally with emails2 / page, +1 / +2 per email
people.phpGET · POSTDecision makers at a domain, ranked for your offer2 / person with email, 1 without
search.phpGETLeads from search engine results0–10
crawl.phpGETEmails, phones and social links from a website1 per valid email
ads.phpGETCompanies running search ads, with contact data0–10
places/search.phpGETRaw maps listings2 / page
places/reviews.phpGETReviews of a place0 or 1
verify.phpPOSTVerify up to 20 emails0
projects.phpGET · POSTList / create projects0
leads.phpGET · POSTList / add leads in a project0
accounts.phpGETConnected sending accounts0
campaigns.phpGET · POSTList, read, create campaigns0
campaigns/generate.phpPOSTWrite a sequence with AI1 per email (2–6)
campaigns/steps.phpPUTReplace a campaign's emails0
campaigns/launch.phpPOSTLaunch a draft campaign0 (sending: 1 / prospect)
campaigns/control.phpPOSTPause, resume or cancel0
campaigns/status.phpGETCampaign progress0
campaigns/recipients.phpGETRecipients, replies and their category0
Getting started

Get your API key

Your API key is the license of your LeadGen.tools account. It identifies the account, so every call works on your data and spends your credits.

  1. Sign in to LeadGen.toolsOpen the app with your account (or create a free one).
  2. Open “API & Agents”In the sidebar, open Advanced tools → API & Agents. Go there now →
  3. Copy the keyClick Reveal or Copy. Your licenses are also listed at leadgen.tools/members/softsale/license.
Treat it like a password. Send it only from your server, a script or an agent you control — never from a public web page, a mobile app or a public repository. If it leaks, contact support to have it replaced.
  • Send it as the api_key query-string parameter on every request.
  • API calls spend the same credit balance as the web app. Top up from your account.
  • In a team, only the account owner and admins can see the key in the app.
  • No key listed? The API & Agents page tells you how to get one, or write to us.
Getting started

Quickstart

Your first call in about a minute: 20 dental clinics in Austin, TX.

  1. Save your key in an environment variable
    Terminal
    export LEADGEN_API_KEY="your_api_key"
  2. Ask for companiesThe request shown here returns one page of ~20 real businesses with website, phone, address, rating and coordinates. It costs 2 credits.
  3. Add emailsAdd with_emails=true to read each company's website for an email (+1 credit per email found, +2 when it comes verified from our data partners).
  4. Keep goingPass next_page_token back as page_token for more, then find the decision makers and launch a campaign.
Repeating a call is safe. Pages, emails and people you already paid for cost 0 for 30 days, so a retry after a timeout never charges twice.
curl -G "https://mail.leadgen.tools/v2/api/v1/companies.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "query=dental clinics" \
  --data-urlencode "location=Austin, TX" \
  --data-urlencode "limit=20"
Response (1 of 20 companies shown) 200 OK
{
  "status": "success",
  "credits_used": 2,
  "data": {
    "query": "dental clinics",
    "location": "Austin, TX",
    "search": "dental clinics in Austin, TX",
    "results_count": 20,
    "with_emails": false,
    "emails_found": null,
    "next_page_token": "c:eyJxIjoiZGVudGFsIGNsaW5pY3MgaW4gQXVzdGluLCBUWCIs...",
    "credits_breakdown": { "pages": 2, "emails": 0, "verified_emails": 0 },
    "companies": [
      {
        "id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
        "name": "Lakeside Family Dental",
        "website": "https://www.lakesidefamilydental.com/",
        "phone": "(512) 555-0142",
        "address": "1200 Barton Springs Rd, Austin, TX 78704",
        "rating": 4.8,
        "reviews": 312,
        "lat": 30.2616,
        "lng": -97.7593,
        "categories": ["Dentist", "Cosmetic dentist"],
        "hours": ["Monday: 8 AM-5 PM", "Tuesday: 8 AM-5 PM"],
        "maps_url": "https://maps.google.com/?cid=1234567890123456789",
        "domain": "lakesidefamilydental.com"
      }
    ]
  }
}
Getting started

Authentication

Every request carries your key in the api_key query-string parameter. The account is resolved from the key — you don't send a user id.

The key goes in the query string, also on POST
curl "https://mail.leadgen.tools/v2/api/v1/campaigns.php?api_key=$LEADGEN_API_KEY"

curl -X POST "https://mail.leadgen.tools/v2/api/v1/verify.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["jane@example.com"]}'
  • The key is checked on every call. A missing key returns 401 missing_api_key; an invalid, expired or inactive one 401 invalid_api_key. If the license check can't be reached you get 503 auth_error — retry shortly.
  • Every call needs a balance of at least 1 credit, including the free ones (projects, leads, status…). With 0 credits every call answers 402 insufficient_credits.
  • All data is scoped to the key's account: you only see and change your own projects, leads, sending accounts and campaigns. Anything else answers 404 not_found.
Getting started

Credits & pricing

Calls spend credits from your LeadGen.tools balance. You are charged for what is delivered: empty results cost 0, and errors cost 0.

EndpointCreditsDetails
Companies2 per pagePer maps page of ~20 companies delivered. With with_emails: +1 per company whose email was found on its website, +2 per company whose email came verified from our data partners (grade A / A-). Each page and each email is charged once per account for 30 days.
People2 / 1 per person2 per person delivered with a verified email (A / A-), 1 per person without an email (lookup=false). Nobody found = 0. Each person charged once per account for 30 days.
Lead search0–1010 when more than 7 results come back; otherwise 1 per result with an email. 0 for no results or a cached answer.
Website crawler0–151 per unique valid email on the site's own domain; 1 if only other emails were found; 0 if none or cached.
Ad Spy0–1010 when more than 7 fresh ads come back; otherwise 1 per fresh ad whose text shows an email. Historical ads and landing-page contact data are free.
Places search2 per pagePer page of up to 20 places; 0 if the page is empty.
Place reviews0 or 11 when reviews are fetched; 0 when they were fetched in the last 7 days.
AI email generation2–61 per email written (the first email + each follow-up), minimum 2.
Verify, projects, leads, accounts, campaigns, steps, launch, control, status, recipients0Free (a balance of at least 1 credit is still required to call them).
Campaign sending1 per prospectCharged by the sender when a prospect gets its first email; the follow-ups to that prospect are included.

Never more than your balance

Companies and People count the cost as they go and never spend more than you have. When the balance runs short they return what you can pay for and add a warning to data (for example “Not enough credits: 3 email(s) withheld…”); on Companies each withheld company has email_withheld: true. Top up and repeat the same call: what you already paid for stays free. If even the first page costs more than your balance, you get 402 insufficient_credits.

AI generation checks the full price first and answers 402 if your balance is lower.

Tip: read credits_used on every response, and data.credits_breakdown on Companies (pages, emails, verified_emails).
Getting started

Errors

Errors use the HTTP status and a JSON body with status: "error", a stable code and a readable message. Branch on code, show message.

HTTPcodeMeaning
400missing_paramA required parameter or body field is missing.
400invalid_paramA value is not accepted (unknown search engine, bad page_token, too many items, unknown status or category…).
400invalid_bodyThe endpoint needs a JSON body and none was sent.
400invalid_jsonThe body is not valid JSON.
400invalid_codeLead search: the recipe code doesn't exist.
400invalid_stateThe campaign can't do that in its current status (e.g. launching a campaign that isn't a draft), or it has no steps / no valid sending account.
400campaign_errorCreating a campaign or replacing its steps failed (e.g. a step without subject or body_html). Nothing was saved.
400launch_errorLaunch failed, e.g. “No valid recipients found in project leads.” Nothing changed.
401missing_api_keyNo api_key parameter.
401invalid_api_keyThe key is invalid, expired or inactive.
402insufficient_creditsBalance below 1 credit, or below the price of the call.
404not_foundThe project, campaign or sending account doesn't exist in your account.
404invalid_user_idThe account behind the key could not be loaded.
405method_not_allowedWrong HTTP method for that endpoint.
409duplicate_projectA project with that name already exists.
500db_errorInternal database error. Retry later.
500config_errorAI generation is not configured on this server.
502provider_errorCompanies: the maps data source is unavailable. No credits deducted.
502api_errorPlaces search / reviews: the maps data source returned an error. No credits deducted.
502search_errorLead search / Ad Spy: the search request failed. Retry after a few seconds.
502crawl_errorWebsite crawler: the URL could not be fetched.
502ai_errorAI generation failed or returned an unusable answer. No credits deducted — try again.
503auth_errorThe license check is unreachable. Retry later.
503not_availablePeople search is not available right now, or reply categories are not enabled on this server (Recipients with category).

Retrying

  • 4xx: fix the request; the same call gives the same answer (except 402 after a top-up).
  • 5xx: retry with a growing pause (for example 5 s, 30 s, 2 min). Companies and People are safe to repeat — already-paid items cost 0.
  • Network timeout on a slow call: repeat it; see Rate limits for sensible timeouts.
Getting started

Rate limits & fair use

There is no per-minute request quota on API v1 today. What is enforced is the size of each call:

EndpointLimit per call
Companieslimit 1–60 companies; up to 5 maps pages read; up to 30 partner email lookups; query / location ≤ 150 characters
Peoplewant 1–3 people; offer ≤ 300, ideal ≤ 200, company ≤ 120 characters
Website crawlerUp to 15 pages of the site
Ad SpyUp to 15 landing pages read per call (the rest on later calls)
Places searchAbout 20 places per page, up to 10 pages per search
Verify20 emails
Leads (POST)500 leads
Leads / Recipients (GET)limit up to 500 (default 100), page with offset
Projects (GET)500 most recent
Generate1–5 follow-ups

Recommendations

  • Call slow endpoints one at a time. Companies with emails (30–120 s for a full call), People (up to about a minute per domain), the crawler (up to 2 min) and Verify (a few seconds per email) do real work on every call.
  • Use an HTTP timeout of at least 240 s for Companies and 180 s for People and the crawler.
  • Keep other calls under 5 at a time, and don't poll campaign status more than every few minutes — the sender works in the background.
  • Reuse results: identical calls are cached (see Caching) and already-paid items are free.

Campaign sending has its own limits: each campaign's daily_limit, each sending account's daily limit, and your plan. See Launch.

Getting started

Caching

Results are cached so repeated work is fast and, where noted, free.

EndpointCacheEffect on credits
CompaniesMaps pages 7 days · website contacts 14 daysYou pay each page / email once per 30 days; repeats cost 0
People30 days (same domain, company, want, offer, ideal, lang, lookup)Each person once per 30 days
Lead search7 days (same query, engine, page, results_per_page)Cached answers cost 0
Website crawler14 days (same URL)Cached answers cost 0
Ad SpyNo response cache: every call searches again. Ads are stored permanently; landing pages are re-read after 7 daysCharged on the fresh results of each call
Places searchAlways live—
Place reviews7 days per placeCached answers cost 0 (cached: true)
AI generation, campaigns, workspaceNever cached—
Prospecting

Companies

GET/api/v1/companies.php
POST/api/v1/companies.php

Real businesses for a niche and a place, from maps data — up to 60 per call, paginated — optionally with the email of each one. Emails are read from each company's own website first; when the site shows none, our data partners are asked for a verified one.

🪙
2 per maps page of ~20 companies delivered. With with_emails: +1 per email found on the website, +2 per verified email from our data partners. Each page and email is billed once per account for 30 days.

Parameters

querystringrequired*
What businesses, e.g. dental clinics. *Not needed when you send page_token. Max 150 characters.
locationstringoptional
Where, e.g. Austin, TX. The search becomes “query in location”. Max 150 characters.
limitintegeroptionaldefault 20
Companies to return, 1–60.
with_emailsbooleanoptionaldefault false
Read each company's website for emails and social links.
fill_missingbooleanoptionaldefault true
With with_emails: when the website shows no email, ask our data partners for a verified one (up to 30 per call).
page_tokenstringoptional
The next_page_token of a previous response, passed back exactly as received. Continues exactly where it stopped.

Response fields

FieldDescription
searchThe search that was run (“query in location”).
results_countCompanies in this response.
emails_foundCompanies with an email (null without with_emails).
next_page_tokenPass as page_token for more; null when there are no more.
credits_breakdownCredits by kind: pages, emails, verified_emails.
companies[]id, name, website, domain, phone, address, rating, reviews, lat, lng, categories, hours, maps_url.
+ with with_emailsemail (the best one), email_grade, email_verified, emails (all found), socials (profile URLs), description (from the site), and email_withheld: true when your balance ran short.
warningOnly when credits ran short (emails withheld or stopped early).

Email grades

A verifiedA- very likely deliverablenull published on the company's website, not verified

email_verified is true only for grade A. Run null-grade emails through Verify before sending.

Notes

  • The best email prefers the company's own domain and general inboxes (info@, contact@) over no-reply or jobs addresses.
  • Social pages and directories listed as a “website” are not read for emails.
  • A page is billed as a whole: asking for limit=5 pays the page, and the next call with page_token continues on that same page for free.
  • With with_emails a full call can take 30–120 s. Use a 240 s timeout.
curl -G "https://mail.leadgen.tools/v2/api/v1/companies.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "query=dental clinics" \
  --data-urlencode "location=Austin, TX" \
  --data-urlencode "limit=20" \
  --data-urlencode "with_emails=true"
Response (2 of 20 companies, some fields omitted) 200 OK
{
  "status": "success",
  "credits_used": 22,
  "data": {
    "query": "dental clinics",
    "location": "Austin, TX",
    "search": "dental clinics in Austin, TX",
    "results_count": 20,
    "with_emails": true,
    "emails_found": 17,
    "next_page_token": "c:eyJxIjoiZGVudGFsIGNsaW5pY3MgaW4gQXVzdGluLCBUWCIs...",
    "credits_breakdown": { "pages": 2, "emails": 14, "verified_emails": 6 },
    "companies": [
      {
        "id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
        "name": "Lakeside Family Dental",
        "website": "https://www.lakesidefamilydental.com/",
        "phone": "(512) 555-0142",
        "address": "1200 Barton Springs Rd, Austin, TX 78704",
        "rating": 4.8,
        "reviews": 312,
        "lat": 30.2616,
        "lng": -97.7593,
        "categories": ["Dentist", "Cosmetic dentist"],
        "hours": ["Monday: 8 AM-5 PM", "Tuesday: 8 AM-5 PM"],
        "maps_url": "https://maps.google.com/?cid=1234567890123456789",
        "domain": "lakesidefamilydental.com",
        "email": "hello@lakesidefamilydental.com",
        "email_verified": false,
        "email_grade": null,
        "emails": ["hello@lakesidefamilydental.com"],
        "socials": ["https://www.facebook.com/lakesidefamilydental"],
        "description": "Gentle family and cosmetic dentistry in South Austin."
      },
      {
        "id": "ChIJ2eUgeAK1RIYRz2hYd0vBqJc",
        "name": "Eastside Smiles",
        "website": "https://eastsidesmiles.com/",
        "domain": "eastsidesmiles.com",
        "email": "maria.lopez@eastsidesmiles.com",
        "email_verified": true,
        "email_grade": "A",
        "emails": ["maria.lopez@eastsidesmiles.com"]
      }
    ]
  }
}
Prospecting

People (decision makers)

GET/api/v1/people.php
POST/api/v1/people.php

The people who decide at a company, ranked by AI for what you sell, with verified emails. Give it the company's domain and your offer; it returns the best contacts first, each with a one-line reason.

🪙
2 per person delivered with a verified email (grade A / A-). 1 per person without an email (lookup=false). Nobody found = 0. Each person is billed once per account for 30 days.

Parameters

domainstringrequired
Company website or domain: acme.com, https://www.acme.com/about…
companystringoptional
Company name — helps the ranking. Max 120 characters.
wantintegeroptionaldefault 1
People to return, 1–3.
offerstringoptional
What you sell (max 300 characters). People are ranked by who decides on buying it.
idealstringoptional
Your ideal customer (max 200 characters).
langstringoptionaldefault en
Language of the reason: en or es.
lookupbooleanoptionaldefault true
true: only people with a verified email, looking the email up when needed. false: names and titles (plus an email when one is already known), no email lookups — cheaper, for research.

Response fields

FieldDescription
candidatesHow many people were considered before ranking.
people_countPeople returned.
people[]rank (1 = best), name, first_name, last_name, title, email, email_verified (grade A), grade (A / A- / null), linkedin, reason.
messageWhen nobody was found, what to try next.
warningWhen your balance ran short and people were withheld.

Notes

  • Only A and A- emails are ever returned. If no decision maker has one, people is empty: use lookup=false for names, or the company's general email from Companies.
  • Default to one contact per company (want=1). When you contact several people at one company, send them separate emails.
  • Can take up to about a minute per domain when emails have to be looked up. Call it one domain at a time.
  • 503 not_available means decision-maker search is off right now; no credits are deducted.
curl -G "https://mail.leadgen.tools/v2/api/v1/people.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "domain=eastsidesmiles.com" \
  --data-urlencode "company=Eastside Smiles" \
  --data-urlencode "want=1" \
  --data-urlencode "offer=Online booking software for dental clinics"
Response 200 OK
{
  "status": "success",
  "credits_used": 2,
  "data": {
    "domain": "eastsidesmiles.com",
    "company": "Eastside Smiles",
    "lookup": true,
    "candidates": 6,
    "people_count": 1,
    "people": [
      {
        "rank": 1,
        "name": "Maria Lopez",
        "first_name": "Maria",
        "last_name": "Lopez",
        "title": "Owner & Lead Dentist",
        "email": "maria.lopez@eastsidesmiles.com",
        "email_verified": true,
        "grade": "A",
        "linkedin": "https://www.linkedin.com/in/maria-lopez-dds",
        "reason": "Owner; decides on practice software"
      }
    ]
  }
}
Prospecting

Website crawler

GET/api/v1/crawl.php

Reads a website — the page you give plus up to 14 of its internal pages — and returns emails, phone numbers, social links and the page's metadata. Placeholder and junk addresses are filtered out.

🪙
1 per unique valid email (on the site's own domain). 1 if only other emails were found. 0 if no email was found, or for a cached answer (14 days).

Parameters

domainstringrequired
The URL to read, e.g. https://example.com or example.com (https:// is added).

Response fields

FieldDescription
domainThe URL that was read.
pages_crawledPages read (max 15).
metadatatitle, description, keywords, image of the first page.
emailsValid emails on the site's own domain.
all_emailsEvery email found, including other domains.
phones, social_linksPhone numbers and social profile URLs.

Can take up to 2 minutes. 502 crawl_error when the first page can't be fetched.

curl -G "https://mail.leadgen.tools/v2/api/v1/crawl.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "domain=https://lakesidefamilydental.com"
Response 200 OK
{
  "status": "success",
  "credits_used": 2,
  "data": {
    "domain": "https://lakesidefamilydental.com",
    "pages_crawled": 12,
    "metadata": {
      "title": "Lakeside Family Dental | Austin Dentist",
      "description": "Gentle family and cosmetic dentistry in South Austin.",
      "keywords": "",
      "image": "https://lakesidefamilydental.com/og.jpg"
    },
    "emails": ["hello@lakesidefamilydental.com", "billing@lakesidefamilydental.com"],
    "all_emails": ["hello@lakesidefamilydental.com", "billing@lakesidefamilydental.com"],
    "phones": ["(512) 555-0142"],
    "social_links": [
      "https://www.facebook.com/lakesidefamilydental",
      "https://www.instagram.com/lakesidedental"
    ]
  }
}
Prospecting

Ad Spy

GET/api/v1/ads.php

Finds the companies running search ads for a keyword — businesses that already spend on marketing. Every call searches again; every ad ever seen for that keyword is kept, so you also get the history. Each ad's landing page is read for emails, phones and social links.

🪙
10 when more than 7 fresh ads come back; otherwise 1 per fresh ad whose text shows an email. Historical ads and landing-page contact data are free.

Parameters

querystringrequired
The keyword advertisers bid on, e.g. emergency plumber chicago.
sestringoptionaldefault google
Search engine: google or bing.
pageintegeroptionaldefault 0
Result offset.
include_historybooleanoptionaldefault true
true: every ad ever seen for this keyword on any engine. false: only the ads seen on se.

Response fields

fresh_count (ads in today's results), historical_count, results_count and results[]:

FieldDescription
position, title, url, ad_text, search_engineThe ad as last seen.
first_seen, last_seenWhen it was first and last seen for this keyword.
is_activetrue when the ad is in this call's results.
contact_dataemails, phones, social_links, metadata from the landing page (read up to 15 pages per call, refreshed after 7 days; empty until read).

The first call for a keyword can take 30–60 s while landing pages are read.

curl -G "https://mail.leadgen.tools/v2/api/v1/ads.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "query=emergency plumber chicago" \
  --data-urlencode "se=google"
Response (1 of 7 ads shown) 200 OK
{
  "status": "success",
  "credits_used": 1,
  "data": {
    "query": "emergency plumber chicago",
    "search_engine": "google",
    "page": 0,
    "fresh_count": 4,
    "historical_count": 3,
    "results_count": 7,
    "results": [
      {
        "position": 1,
        "title": "24/7 Emergency Plumber | Fast Response",
        "url": "https://www.rapidflowplumbing.example/",
        "ad_text": "Sponsored · Licensed plumbers in 60 minutes. Call now or email help@rapidflowplumbing.example",
        "search_engine": "google",
        "first_seen": "2026-08-02 14:10:22",
        "last_seen": "2026-09-27 09:31:05",
        "is_active": true,
        "contact_data": {
          "emails": ["help@rapidflowplumbing.example"],
          "phones": ["(312) 555-0110"],
          "social_links": ["https://www.facebook.com/rapidflowplumbing"],
          "metadata": { "title": "Rapid Flow Plumbing", "description": "Emergency plumbing in Chicago.", "keywords": "", "image": "" }
        }
      }
    ]
  }
}
Prospecting

Place reviews

GET/api/v1/places/reviews.php

Recent reviews of a place — useful to personalize an opening line or qualify a business.

🪙
1 when reviews are fetched; 0 when they were fetched in the last 7 days (cached: true).

Parameters

place_idstringrequired
The place id from Places search or id from Companies.

Response fields

place_id, cached and reviews[] with author_name, author_url, profile_photo_url, rating, text, relative_time_description, time (Unix timestamp).

curl -G "https://mail.leadgen.tools/v2/api/v1/places/reviews.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "place_id=ChIJx8Qm3gC32YgR0bXg4v1a2bc"
Response 200 OK
{
  "status": "success",
  "credits_used": 1,
  "data": {
    "place_id": "ChIJx8Qm3gC32YgR0bXg4v1a2bc",
    "reviews": [
      {
        "author_name": "Daniel R.",
        "author_url": null,
        "profile_photo_url": null,
        "rating": 5,
        "text": "Best ropa vieja in town, and the staff remembered our names.",
        "relative_time_description": "2 weeks ago",
        "time": 1757548800
      }
    ],
    "cached": false
  }
}
Data quality

Verify emails

POST/api/v1/verify.php

Checks up to 20 emails: format, domain, MX / A records, SPF, DKIM, DMARC, disposable domains and whether the mailbox accepts mail. Optionally stores the result on your leads.

🪙
Free.

Body (JSON)

emailsstring[]required
Emails to check, max 20 per call.
lead_idsinteger[]optional
Lead ids from Leads, in the same order as emails. Each lead's verified becomes 1 (valid) or -1 (invalid).

Response fields

verified, valid, invalid counts and results[]: email, valid, verdict (valid · invalid · invalid_format) and details — domain_exists, valid_mx, disposable, has_a, has_spf, has_dkim, has_dmarc, deliverable.

Notes

  • valid follows the mailbox check (deliverable). Some servers accept every address; others refuse checks — treat results as a strong signal, not a guarantee.
  • A few seconds per email: send batches of 20 one after another, with a generous timeout.
  • Emails graded A by People or Companies are already verified.
curl -X POST "https://mail.leadgen.tools/v2/api/v1/verify.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "emails": [
    "hello@lakesidefamilydental.com",
    "info@nosuchdomain-xyz.example"
  ],
  "lead_ids": [1841, 1842]
}'
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "verified": 2,
    "valid": 1,
    "invalid": 1,
    "results": [
      {
        "email": "hello@lakesidefamilydental.com",
        "valid": true,
        "details": {
          "domain_exists": true,
          "valid_mx": true,
          "disposable": false,
          "has_a": true,
          "has_spf": true,
          "has_dkim": false,
          "has_dmarc": true,
          "deliverable": true
        },
        "verdict": "valid"
      },
      {
        "email": "info@nosuchdomain-xyz.example",
        "valid": false,
        "details": {
          "domain_exists": false,
          "valid_mx": false,
          "disposable": false,
          "has_a": false,
          "has_spf": false,
          "has_dkim": false,
          "has_dmarc": false,
          "deliverable": false
        },
        "verdict": "invalid"
      }
    ]
  }
}
Workspace

Projects

GET/api/v1/projects.php
POST/api/v1/projects.php

Projects are the prospect lists of your account — the same ones you see under Prospects in the app. A campaign sends to the leads of one project.

🪙
Free.

GET — query parameters

searchstringoptional
Only projects whose name contains this text.

Returns up to the 500 most recent projects: projects_count and projects[] with id, name, type and campaigns[] (id, name, status) already using it.

POST — body (JSON)

namestringrequired
Project name. Must be unique in your account (409 duplicate_project otherwise).

Returns project_id and name.

Tip: look for an existing project with ?search= before creating one.
curl -X POST "https://mail.leadgen.tools/v2/api/v1/projects.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Dental clinics Austin — Sept"
}'
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "project_id": 412,
    "name": "Dental clinics Austin — Sept"
  }
}
curl -G "https://mail.leadgen.tools/v2/api/v1/projects.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "search=Dental"
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "projects_count": 1,
    "projects": [
      {
        "id": 412,
        "name": "Dental clinics Austin — Sept",
        "type": "",
        "campaigns": [
          { "id": 88, "name": "Austin dentists — booking", "status": "active" }
        ]
      }
    ]
  }
}
Workspace

Leads

GET/api/v1/leads.php
POST/api/v1/leads.php

The prospects inside a project. Add up to 500 per call; duplicates (same email in the same project) are skipped.

🪙
Free.

GET — query parameters

project_idintegerrequired
The project.
limitintegeroptionaldefault 100
1–500.
offsetintegeroptionaldefault 0
For paging.

Returns total, limit, offset, leads_count and leads[] (newest first): id, email, firstname, lastname, company, website, phones, description, verified (1 valid · -1 invalid · 0 not checked), keyword, social_media.

POST — body (JSON)

project_idintegerrequired
The project to add to.
leadsobject[]required
Up to 500 leads. Each: email (required), firstname, lastname, company (or title), website (or url), phones (or phone), description, social_media.

Returns imported, duplicates, errors and details[] (one line per rejected row).

Notes

  • Without a first or last name, one is guessed from the email (john.doe@ → John Doe). Pass real names when you have them — they go in {firstname}.
  • company fills the {company} merge field and website the {website} one; the website is also what the personal first line ({icebreaker}) is written from.
  • Imported leads have keyword = api-import.
curl -X POST "https://mail.leadgen.tools/v2/api/v1/leads.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "project_id": 412,
  "leads": [
    {
      "email": "maria.lopez@eastsidesmiles.com",
      "firstname": "Maria",
      "lastname": "Lopez",
      "company": "Eastside Smiles",
      "website": "https://eastsidesmiles.com"
    },
    {
      "email": "hello@lakesidefamilydental.com",
      "company": "Lakeside Family Dental",
      "website": "https://lakesidefamilydental.com",
      "phone": "(512) 555-0142"
    }
  ]
}'
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "imported": 2,
    "duplicates": 0,
    "errors": 0,
    "details": []
  }
}
Workspace

Sending accounts

GET/api/v1/accounts.php

The mailboxes your campaigns can send from: Gmail / Google Workspace accounts and SMTP accounts connected in the app. Campaigns go out from these accounts, so replies land in your own inbox.

🪙
Free.

Parameters

typestringoptional
gmail or smtp to list only one kind.

Response fields

accounts_count and accounts[]:

  • Both kinds: type, id, email, name, daily_send_limit, sent_today, status.
  • Gmail status: connected or reconnect_required.
  • SMTP adds label, smtp_host, smtp_port, smtp_encryption, has_imap (replies can be detected), is_verified; status is verified or unverified.
Accounts are connected in the app, not through the API. Send the user to LeadGen.tools → My email to connect Gmail in one click or another provider over SMTP. Passwords and tokens are never returned.
curl -G "https://mail.leadgen.tools/v2/api/v1/accounts.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY"
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "accounts_count": 2,
    "accounts": [
      {
        "type": "gmail",
        "id": 100000017,
        "email": "sam@brightbooking.example",
        "name": "Sam Carter",
        "daily_send_limit": 500,
        "sent_today": 12,
        "status": "connected"
      },
      {
        "type": "smtp",
        "id": 23,
        "email": "sam@getbrightbooking.example",
        "name": "Sam Carter",
        "label": "Outreach domain",
        "smtp_host": "smtp.zoho.com",
        "smtp_port": 587,
        "smtp_encryption": "tls",
        "daily_send_limit": 40,
        "sent_today": 0,
        "has_imap": true,
        "is_verified": 1,
        "status": "verified"
      }
    ]
  }
}
Campaigns

Campaigns

GET/api/v1/campaigns.php
POST/api/v1/campaigns.php

A campaign sends a sequence of emails (a first email and follow-ups) to the leads of one project, from one or more of your sending accounts. POST creates it as a draft; nothing is sent until you launch it.

🪙
Free. Sending costs 1 credit per prospect contacted, follow-ups included.

POST — body (JSON)

namestringrequired
Campaign name.
project_idintegerrequired
The project whose leads will be contacted.
sending_accountsobject[]required
At least one: {"type": "gmail" | "smtp", "id": 123} from Sending accounts. First emails rotate across them.
stepsobject[]required
The emails, in order. Each: step_order (0 = first email, 1+ = follow-ups; defaults to its position), subject, body_html, delay_days (days to wait after the previous email; 0 for the first). See merge fields & spintax.
daily_limitintegeroptionaldefault 50
Maximum emails this campaign sends per day.
batch_sizeintegeroptionaldefault 5
Stored with the campaign.

Returns campaign_id, name and status: "draft". A step without subject or body_html answers 400 campaign_error and nothing is saved.

GET — list and detail

  • GET campaigns.php: campaigns_count and campaigns[] (newest first) with id, name, status, project_id, project_name, accounts_display, daily_limit, total_sent, total_replied, total_bounced, total_recipients, created_at, launched_at.
  • GET campaigns.php?id=88: campaign with the same fields plus batch_size, updated_at, steps[], recipient_stats and sending_accounts[] (type, id, email, label).

Campaign status: draftactivepausedcompletedcancelled

curl -X POST "https://mail.leadgen.tools/v2/api/v1/campaigns.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Austin dentists — booking",
  "project_id": 412,
  "daily_limit": 40,
  "sending_accounts": [
    {
      "type": "gmail",
      "id": 100000017
    },
    {
      "type": "smtp",
      "id": 23
    }
  ],
  "steps": [
    {
      "step_order": 0,
      "subject": "{Quick question|Idea} for {company}",
      "body_html": "<p>{Hi|Hello} {firstname|there},</p><p>{icebreaker}</p><p>We help clinics like {company} fill empty chairs with online booking. {Worth a quick look?|Open to a 10-minute call?}</p><p>{sender_name}</p>",
      "delay_days": 0
    },
    {
      "step_order": 1,
      "subject": "Re: {Quick question|Idea} for {company}",
      "body_html": "<p>{Just checking in|Following up} on my note, {firstname|there}. {Any interest?|Should I send a 2-minute video?}</p><p>{sender_name}</p>",
      "delay_days": 3
    }
  ]
}'
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "campaign_id": 88,
    "name": "Austin dentists — booking",
    "status": "draft"
  }
}
Campaigns

Generate emails with AI

POST/api/v1/campaigns/generate.php

Writes a cold email sequence — the first email plus 1–5 follow-ups — from a short brief. The emails use merge fields, a <p>{icebreaker}</p> line in the first email and plenty of spintax so no two prospects get the same text. It returns the steps; it doesn't save anything. Review them, then pass them to Campaigns or Update steps.

🪙
1 per email written: 1 + num_followups, minimum 2 (so 2–6). Charged only when the emails come back.

Body (JSON)

promptstringrequired
The brief: what you sell, to whom, proof, the call to action, tone and language. The more specific, the better.
num_followupsintegeroptionaldefault 2
Follow-ups after the first email, 1–5.
custom_system_promptstringoptional
Replaces the default instructions given to the AI, for this request only.
custom_user_prompt_templatestringoptional
Replaces the default request template for this call. Use {num_followups} and {prompt} as placeholders.

Returns steps[] with step_order, subject, body_html, delay_days.

Tip: the emails are signed with {sender_name}, filled from the sending account when each email goes out. Unsubscribe lines are added automatically — don't ask for them.
curl -X POST "https://mail.leadgen.tools/v2/api/v1/campaigns/generate.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "prompt": "We sell online booking software for dental clinics. Proof: clinics fill 20% more chairs. Goal: a 15-minute demo. Friendly, short, English.",
  "num_followups": 2
}'
Response (bodies shortened) 200 OK
{
  "status": "success",
  "credits_used": 3,
  "data": {
    "steps": [
      {
        "step_order": 0,
        "subject": "{Quick question|A thought|Idea} for {company}",
        "body_html": "<p>{Hi|Hey|Hello} {firstname},</p><p>{icebreaker}</p><p>{Clinics like yours|Practices like {company}} {fill|book} about 20% more chairs once patients can book online...</p><p>{Open to|Up for} a {15-minute|quick} demo {this week|next week}?</p><p>{Best|Cheers},<br>{sender_name}</p>",
        "delay_days": 0
      },
      {
        "step_order": 1,
        "subject": "Re: {Quick question|A thought|Idea} for {company}",
        "body_html": "<p>{Just following up|Circling back}, {firstname}...</p>",
        "delay_days": 3
      },
      {
        "step_order": 2,
        "subject": "Re: {Quick question|A thought|Idea} for {company}",
        "body_html": "<p>{Last note from me|One last try}...</p>",
        "delay_days": 4
      }
    ]
  }
}
Campaigns

Update steps

PUT/api/v1/campaigns/steps.php

Replaces all the emails of a campaign with the ones you send. Only for draft or paused campaigns (pause it first with control). Emails already sent stay as they were; future sends use the new text.

🪙
Free.

Body (JSON)

campaign_idintegerrequired
The campaign.
stepsobject[]required
The full new sequence, same shape as in Campaigns: step_order, subject, body_html, delay_days.

Returns campaign_id, steps_count, message. Other statuses answer 400 invalid_state.

curl -X PUT "https://mail.leadgen.tools/v2/api/v1/campaigns/steps.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "campaign_id": 88,
  "steps": [
    {
      "step_order": 0,
      "subject": "{Quick question|Idea} for {company}",
      "body_html": "<p>{Hi|Hello} {firstname|there},</p><p>{icebreaker}</p><p>...</p>",
      "delay_days": 0
    },
    {
      "step_order": 1,
      "subject": "Re: {Quick question|Idea} for {company}",
      "body_html": "<p>{Just checking in|Following up}...</p>",
      "delay_days": 4
    }
  ]
}'
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "campaign_id": 88,
    "steps_count": 2,
    "message": "Steps updated successfully."
  }
}
Campaigns

Launch

POST/api/v1/campaigns/launch.php

Starts a draft campaign: loads every lead of its project that has an email as a recipient and sets the campaign active. From then on LeadGen.tools sends in the background.

🪙
Launching is free. The sender charges 1 credit per prospect when its first email goes out; its follow-ups are included.

Body (JSON)

campaign_idintegerrequired
A campaign in draft status.

Returns campaign_id, status: "active" and recipients_loaded.

Before launching

  • The campaign needs at least one step and one valid sending account (400 invalid_state otherwise).
  • People who unsubscribed or said they weren't interested in an earlier campaign of yours are never loaded. No recipient left → 400 launch_error.
  • For a lead with several emails, the first one is used.

How sending works

  • Emails go out in business hours on weekdays, a few at a time with human-like pauses, never over the campaign's daily_limit or each account's daily limit.
  • First emails rotate across the campaign's sending accounts; each prospect's follow-ups come from the account that sent its first email, as replies in the same conversation.
  • Follow-ups are sent only to people who haven't replied. Replies are checked automatically and classified by AI (see Recipients).
  • Every email carries a short opt-out line and an unsubscribe header; people who opt out are added to your suppression list.
  • If the bounce rate gets too high the campaign is paused to protect your domain.
  • Out of credits, the campaign isn't paused: follow-ups keep going and new prospects wait until you top up.
curl -X POST "https://mail.leadgen.tools/v2/api/v1/campaigns/launch.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "campaign_id": 88
}'
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "campaign_id": 88,
    "status": "active",
    "recipients_loaded": 37
  }
}
Campaigns

Pause, resume or cancel

POST/api/v1/campaigns/control.php

Changes a running campaign's status.

🪙
Free.

Body (JSON)

campaign_idintegerrequired
The campaign.
actionstringrequired
pause (active → paused), resume (paused → active) or cancel (active or paused → cancelled, final).

Returns campaign_id, previous_status, status, message. Any other transition answers 400 invalid_state.

curl -X POST "https://mail.leadgen.tools/v2/api/v1/campaigns/control.php?api_key=$LEADGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "campaign_id": 88,
  "action": "pause"
}'
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "campaign_id": 88,
    "previous_status": "active",
    "status": "paused",
    "message": "Campaign paused."
  }
}
Campaigns

Status

GET/api/v1/campaigns/status.php

A light summary of a campaign's progress, for polling.

🪙
Free.

Parameters

campaign_idintegerrequired
The campaign.

Returns campaign_id, name, status, daily_limit, total_sent, total_replied, total_bounced, sent_today, steps_count, launched_at, updated_at and recipient_stats:

totalpendingin_progresscompletedrepliedbouncedunsubscribed

pending = waiting for the first email · in_progress = in the sequence · completed = every email sent, no reply.

curl -G "https://mail.leadgen.tools/v2/api/v1/campaigns/status.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "campaign_id=88"
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "campaign_id": 88,
    "name": "Austin dentists — booking",
    "status": "active",
    "daily_limit": 40,
    "total_sent": 61,
    "total_replied": 4,
    "total_bounced": 1,
    "sent_today": 9,
    "steps_count": 3,
    "recipient_stats": {
      "total": 37,
      "pending": 6,
      "in_progress": 24,
      "completed": 2,
      "replied": 4,
      "bounced": 1,
      "unsubscribed": 0
    },
    "launched_at": "2026-09-21 10:02:11",
    "updated_at": "2026-09-27 11:45:40"
  }
}
Campaigns

Recipients & replies

GET/api/v1/campaigns/recipients.php

Everyone in a campaign with where they are in the sequence — and, once they answer, what they said: every reply is classified by AI with a one-line summary.

🪙
Free.

Parameters

campaign_idintegerrequired
The campaign.
statusstringoptional
Only this status: pending, in_progress, completed, replied, bounced, unsubscribed.
categorystringoptional
Only these reply categories: hot (= interested + meeting + question) or a comma list of interested, meeting, question, not_interested, unsubscribe, out_of_office, wrong_person, other. Combines with status.
limitintegeroptionaldefault 100
1–500.
offsetintegeroptionaldefault 0
For paging.

Response fields

total (matching the filters), limit, offset, status_filter, category_filter, recipients_count and recipients[]:

FieldDescription
id, email, firstname, lastnameThe prospect.
status, current_stepWhere they are in the sequence.
last_sent_at, replied_atLast email sent, and when they replied.
sent_via_type, sent_via_account_idThe sending account used (gmail / smtp + id).
reply_categoryWhat the reply is (list below). null until they answer.
reply_summaryOne line: what they said.
reply_textThe text of the reply.

Reply categories

🔥 interested📅 meeting❓ questionnot_interestedunsubscribeout_of_officewrong_personother
  • A reply stops that prospect's sequence (status replied), except out of office: the next email simply waits until they are back.
  • unsubscribe → status unsubscribed; unsubscribe and not_interested are added to your suppression list, so no future campaign contacts them.
  • Answer the hot ones from your own inbox — the replies arrived there.
  • category on a server without reply intelligence answers 503 not_available.
curl -G "https://mail.leadgen.tools/v2/api/v1/campaigns/recipients.php" \
  --data-urlencode "api_key=$LEADGEN_API_KEY" \
  --data-urlencode "campaign_id=88" \
  --data-urlencode "category=hot"
Response 200 OK
{
  "status": "success",
  "credits_used": 0,
  "data": {
    "campaign_id": 88,
    "total": 2,
    "limit": 100,
    "offset": 0,
    "status_filter": null,
    "category_filter": "hot",
    "recipients_count": 2,
    "recipients": [
      {
        "id": 5120,
        "email": "maria.lopez@eastsidesmiles.com",
        "firstname": "Maria",
        "lastname": "Lopez",
        "status": "replied",
        "current_step": 1,
        "last_sent_at": "2026-09-24 10:17:03",
        "replied_at": "2026-09-24 13:40:55",
        "sent_via_type": "gmail",
        "sent_via_account_id": 100000017,
        "reply_category": "meeting",
        "reply_summary": "Wants a demo next Tuesday afternoon",
        "reply_text": "Hi Sam, sounds interesting. Could we do Tuesday after 2pm? Maria"
      },
      {
        "id": 5127,
        "email": "office@hillcountrydental.example",
        "firstname": "Office",
        "lastname": "",
        "status": "replied",
        "current_step": 2,
        "last_sent_at": "2026-09-26 09:05:40",
        "replied_at": "2026-09-26 15:22:10",
        "sent_via_type": "smtp",
        "sent_via_account_id": 23,
        "reply_category": "question",
        "reply_summary": "Asks if it works with their scheduling system",
        "reply_text": "Does this connect with our current scheduling software?"
      }
    ]
  }
}
Guide

Build a full outbound pipeline

From “dental clinics in Austin” to replies in your inbox, entirely through the API.

companies→people→verify→projects + leads→generate→campaigns→launch→status→recipients
  1. Find companiesGET companies.php?query=dental clinics&location=Austin, TX&limit=60&with_emails=true. Repeat with page_token until you have enough. Budget: 2 per page + 1–2 per email.
  2. Find the decision maker (optional) — for each company with a domain, GET people.php?domain=…&offer=…&want=1, one at a time. Keep the company email when nobody is found.
  3. Clean — send the emails with email_grade: null to POST verify.php in batches of 20 and drop the invalid ones.
  4. Store — POST projects.php (or reuse one found with ?search=), then POST leads.php with firstname, lastname, company, website for each prospect.
  5. Write — POST campaigns/generate.php with a clear brief. Read and edit the emails before using them.
  6. Create — GET accounts.php to pick the sending accounts, then POST campaigns.php with the project, accounts and steps. It's a draft.
  7. Launch — POST campaigns/launch.php. Sending costs 1 credit per prospect contacted, follow-ups included.
  8. Follow — poll GET campaigns/status.php every few hours, and GET campaigns/recipients.php?category=hot for the people who want to talk.
pipeline.py
import os, time, requests

BASE = "https://mail.leadgen.tools/v2/api/v1/"
KEY = {"api_key": os.environ["LEADGEN_API_KEY"]}

def call(method, path, params=None, body=None, timeout=240):
    r = requests.request(method, BASE + path, params={**KEY, **(params or {})}, json=body, timeout=timeout)
    j = r.json()
    if j["status"] != "success":
        raise RuntimeError(j["code"] + ": " + j["message"])
    print(path, "credits_used =", j["credits_used"])
    return j["data"]

# 1. Companies with emails
found = call("GET", "companies.php", {"query": "dental clinics", "location": "Austin, TX",
                                      "limit": 40, "with_emails": "true"})
leads = []
for c in found["companies"]:
    if not c.get("email"):
        continue
    lead = {"email": c["email"], "company": c["name"], "website": c["website"] or ""}
    # 2. Decision maker (optional, one domain at a time)
    if c.get("domain"):
        p = call("GET", "people.php", {"domain": c["domain"], "company": c["name"], "want": 1,
                                       "offer": "Online booking software for dental clinics"}, timeout=180)
        if p["people"]:
            best = p["people"][0]
            lead.update(email=best["email"], firstname=best["first_name"], lastname=best["last_name"])
    leads.append(lead)

# 3. Verify website emails that are not graded (skipped here for brevity: POST verify.php, 20 at a time)

# 4. Project + leads
project = call("POST", "projects.php", body={"name": "Austin dentists " + time.strftime("%Y-%m-%d")})
call("POST", "leads.php", body={"project_id": project["project_id"], "leads": leads})

# 5. Emails
steps = call("POST", "campaigns/generate.php", body={
    "prompt": "Online booking software for dental clinics. Goal: a 15-minute demo. Friendly, short.",
    "num_followups": 2})["steps"]

# 6. Campaign from every connected account
accounts = [{"type": a["type"], "id": a["id"]} for a in call("GET", "accounts.php")["accounts"]]
camp = call("POST", "campaigns.php", body={"name": "Austin dentists", "project_id": project["project_id"],
                                           "sending_accounts": accounts, "steps": steps, "daily_limit": 40})

# 7. Launch (review first!) and 8. follow the replies later
call("POST", "campaigns/launch.php", body={"campaign_id": camp["campaign_id"]})
hot = call("GET", "campaigns/recipients.php", {"campaign_id": camp["campaign_id"], "category": "hot"})
for r in hot["recipients"]:
    print(r["email"], r["reply_category"], "-", r["reply_summary"])
Ask before you send. Launching emails real people from your mailboxes. Show the list, the emails and the expected cost to whoever owns the account before calling launch.
Guide

Send with your own tool

Already sending with another platform, your own SMTP or a CRM with sequences? Use LeadGen.tools only to find and clean the prospects, and export them.

  1. Find — companies.php with with_emails=true, and people.php when you want a named decision maker.
  2. Clean — verify null-grade emails with verify.php; drop invalid ones and duplicates.
  3. Export — write a CSV or push the rows to your tool's API. Useful columns: company, website, domain, first_name, last_name, title, email, email_grade, phone, address, rating, reviews.
export_csv.py
import csv, os, requests

BASE = "https://mail.leadgen.tools/v2/api/v1/"
KEY = os.environ["LEADGEN_API_KEY"]

data = requests.get(BASE + "companies.php", params={
    "api_key": KEY, "query": "roofing contractors", "location": "Denver, CO",
    "limit": 60, "with_emails": "true"}, timeout=240).json()["data"]

cols = ["company", "website", "domain", "email", "email_grade", "phone", "address", "rating", "reviews"]
with open("prospects.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=cols)
    w.writeheader()
    for c in data["companies"]:
        if c.get("email"):
            w.writerow({"company": c["name"], "website": c["website"], "domain": c["domain"],
                        "email": c["email"], "email_grade": c["email_grade"] or "", "phone": c["phone"],
                        "address": c["address"], "rating": c["rating"], "reviews": c["reviews"]})
Be a good sender. Contact businesses about something relevant to them, include an opt-out line and honor it. LeadGen.tools campaigns do this for you automatically; your own tool may not.
Guide

Merge fields & spintax

Subjects and bodies of campaign steps are personalized for every prospect when each email is sent.

FieldFilled with
{firstname}, {lastname}The lead's first / last name.
{email}The lead's email.
{company}The lead's company (company in Leads).
{website}The lead's website.
{icebreaker}A personal first line about that prospect, written by AI from its website when the first email is sent. Put it alone in a paragraph: <p>{icebreaker}</p>. When there is nothing specific to say, the whole paragraph is removed.
{sender_name}, {sender_email}, {sender_company}The signer and the sending account. An empty company line under the name is removed.

Fallbacks

Write {field|fallback} to use a word when the field is empty: {Hi|Hello} {firstname|team}, becomes “Hi Maria,” or “Hello team,”.

Spintax

{Hi|Hey|Hello} picks one option at random for each email, so no two prospects receive exactly the same text — good for deliverability. Merge fields are filled first; any {a|b|c} left is spintax. Don't nest braces.

body_html
<p>{Hi|Hey|Hello} {firstname|there},</p>
<p>{icebreaker}</p>
<p>{We help|We work with} clinics like {company} {fill more chairs|get more bookings} with online booking.</p>
<p>{Worth a quick look?|Open to a 15-minute demo?}</p>
<p>{sender_name}<br>{sender_company}</p>

Added automatically

  • A short opt-out line (“just reply no”) and an unsubscribe header on every email — don't write your own unsubscribe link.
  • Follow-ups are sent as replies in the same conversation as the first email.
AI agents

AI agents & OpenClaw

The LeadGen.tools skill teaches an AI agent the whole API: which endpoint to use, what things cost, how to page, when to ask before spending, and how to run a campaign end to end. Your agent can then do things like:

Prompt
Find 20 dental clinics in Austin with their owners' emails and draft a 3-email sequence

Install in OpenClaw

  1. Install the skillFrom ClawHub:
    ClawHub
    openclaw skills install @leadgen/leadgen-tools
    Or straight from GitHub:
    GitHub
    openclaw skills install git:leadgentools/leadgen-tools-skill@main
    Or by hand: copy the leadgen-tools folder to ~/.openclaw/skills/leadgen-tools (or <workspace>/skills/leadgen-tools).
  2. Give it your API keyIn ~/.openclaw/openclaw.json:
    ~/.openclaw/openclaw.json
    {
      "skills": {
        "entries": {
          "leadgen-tools": { "apiKey": "YOUR_API_KEY" }
        }
      }
    }
    or as an environment variable: export LEADGEN_API_KEY="…". Where to find your key.
  3. AskStart a new session and describe what you want in plain words — who to target, where, how many, what you sell, and whether to send from LeadGen.tools or export.

Other agents

The skill is a standard SKILL.md folder, so it also works with other agents that read skills. For Claude Code, copy it to ~/.claude/skills/leadgen-tools and set LEADGEN_API_KEY in the environment. Any agent that can make HTTP requests can also use this reference directly.

Spending is real. Agents spend your credits and can email real people. The skill tells the agent to estimate the cost of big jobs and ask before running them, and to get your approval before launching a campaign.
Updates

Changelog

v1.5 September 2026

  • Recipients: new category filter (hot = interested + meeting + question, or a list of categories), and each recipient now has reply_category, reply_summary and reply_text.
  • API & Agents page in the app: see and copy your API key, and connect an AI agent in three steps.
  • New documentation.

v1.4 September 2026

  • Companies: real businesses for a niche + city in one call (up to 60, paginated), optionally with the email of each one — read from its website, or verified by our data partners.
  • People: decision makers at a company domain, ranked by AI for what you sell, with verified emails.
  • Items you already paid for are free to fetch again for 30 days.
  • Agent Skill: a ready-made skill lets AI agents (Claude, OpenClaw…) use the whole API.

v1.3 February 2026

  • Campaign Management API (OpenClaw): full campaign lifecycle via API — create projects, import leads, verify emails, generate AI email sequences, create & launch campaigns, and monitor progress.
  • New endpoints: Projects, Leads, Email Verification, Sending Accounts, Campaigns, AI Email Generation, Update Steps, Launch, Control, Status, Recipients.
  • AI Email Generation: spintax-powered email sequences written by AI. 1 credit per email generated.
  • Email Verification: domain, MX, SPF, DKIM, DMARC, disposable check, and mailbox deliverability validation.

v1.2 February 2026

  • Ad Spy — historical ads: ads are stored permanently. Every search adds new ads to the history. Use first_seen, last_seen and is_active to track how long ads run.
  • Ad Spy — landing-page contact data: each ad's landing page is read for emails, phones, social_links and page metadata, returned in contact_data.
  • Longer caching: search cache from 1 hour to 7 days; crawl cache from 24 hours to 14 days.
  • New parameter: include_history on Ad Spy.

v1.1 January 2026

  • Places search: maps business listings.
  • Place reviews: reviews of a place, cached 7 days.

v1.0 December 2025

  • Initial release with Lead Search, Website Crawler and Ad Spy.
  • License key authentication.
  • Credit-based billing with automatic caching.

LeadGen.tools API v1 · Your API key · Back to top ↑