API Documentation
Integrate qualified revenue opportunities directly into your stack.
Data Pipeline Overview
Every row of an uploaded file goes through these steps:
CSV, Excel or JSON
Standardize
Email + Phone
Website + AI
Fuzzy Match
Hot / Warm / Cold
The results file has every lead that made it through, each with its tier
(hot, warm or cold). Only Hot and Warm leads are charged.
Authentication
Use an API key from
API Keys in the dashboard (sk_live_…), sent as a Bearer token or an
X-API-Key header. A session token from /api/auth/login works the same way.
curl -s https://leedrush.com/api/limits \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# or
curl -s https://leedrush.com/api/limits -H "X-API-Key: sk_live_YOUR_KEY"
Store the token and use -H "Authorization: Bearer $TOKEN" in all subsequent requests.
Base URL
https://leedrush.com/api
All endpoints are relative to this base URL. Use
https://leedrush.com/api/upload, /api/process, etc.
Endpoints
Upload a CSV, Excel (.xlsx) or JSON file with an email
column (multipart, field file). The file is checked and stored; nothing is charged yet.
curl -s -X POST https://leedrush.com/api/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@leads.csv" \
| jq .
{
"filename": "user_17283_leads.csv",
"message": "File uploaded and validated successfully",
"file_hash": "9f2c…",
"validation": {
"file_size": 24576,
"mime_type": "text/csv",
"encoding": "utf-8",
"row_count": 501,
"column_count": 6,
"tier": "pro"
}
}
row_count includes the header row of a CSV or Excel file. Use the returned
filename in the calls below.
What processing an uploaded file can cost, before you start it.
Leedrush charges 1 credit per Hot or Warm lead, at most 500 credits per file and never more than
your balance, so the answer is a range: from 0 (no lead qualifies) to
min(rows, 500, balance).
curl -s -X POST https://leedrush.com/api/estimate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"filename": "user_17283_leads.csv"}' | jq .
{
"filename": "user_17283_leads.csv",
"rows": 500,
"estimated_leads": 500,
"estimated_cost_min": 0,
"estimated_cost_max": 500,
"estimated_cost": 500,
"max_credits_per_file": 500,
"credits_available": 2500,
"can_afford": true,
"balance_limits_charge": false,
"credits_after": 2000,
"credits_after_best": 2500,
"billing": {
"charged_for": ["hot", "warm"],
"credits_per_qualified_lead": 1,
"max_credits_per_file": 500,
"rule": "1 credit per Hot or Warm lead. …"
}
}
estimated_cost is the most the file can cost; credits_after is your
balance in that case. can_afford is true with any positive balance: if more leads
qualify than your balance covers, the rest of the balance is charged and every lead is still
delivered.
Start a background processing job for an uploaded file.
curl -s -X POST https://leedrush.com/api/process \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"filename": "user_17283_leads.csv", "file_hash": "9f2c…"}' \
| jq .
{
"job_id": "batch_a1b2c3d4_1760259600",
"status": "pending",
"message": "Batch processing started (Priority: medium, Mode: celery)"
}
Sending the same file again returns its existing batch ("is_duplicate": true)
unless that batch failed. With a zero balance the answer is 402.
Retrieve a list of all batch jobs and their status.
curl -s "https://leedrush.com/api/batches?limit=20" \
-H "Authorization: Bearer $TOKEN" \
| jq .
{
"batches": [
{
"id": "batch_a1b2c3d4_1760259600",
"display_name": "leads.csv",
"status": "completed",
"progress": 100,
"leads_count": 500,
"saved_count": 431,
"hot_count": 58,
"warm_count": 84,
"cold_count": 289,
"qualified_count": 142,
"total_cost": 142.0,
"created_at": "2026-10-12T11:00:00"
}
]
}
Newest first. limit is optional (all batches without it). status is
pending, processing, completed,
completed_with_errors, failed, timed_out or
cancelled. total_cost is the credits charged.
GET /api/batches/{id}/details returns one batch.
BATCH_ID="batch_a1b2c3d4_1760259600"
while true; do
STATUS=$(curl -s "https://leedrush.com/api/batches/$BATCH_ID/details" \
-H "Authorization: Bearer $TOKEN" | jq -r '.batch.status')
echo "Status: $STATUS"
case "$STATUS" in completed|completed_with_errors|failed|timed_out|cancelled) break;; esac
sleep 10
done
Or skip polling: add a webhook for batch.completed (see Webhook Events below).
Download the results of a batch: CSV by default,
?format=json for JSON.
curl -s -o results.csv "https://leedrush.com/api/batches/$BATCH_ID/export" \
-H "Authorization: Bearer $TOKEN"
email, email_status, first_name, last_name, title, seniority, phone, linkedin_url,
company, domain, industry, company_size, revenue, country, company_linkedin,
score, tier, score_reason, enriched_by
tier is hot, warm or cold;
email_status is the email check's verdict (unverified when there was
none). A field is empty when no source had it.
Your plan, its file limits, your credit balance and the billing rule.
{
"plan": "scale",
"plan_id": "scale",
"plan_name": "Scale",
"limits": { "max_file_mb": 500, "max_rows": 1000000, "max_columns": 200 },
"priority_queue": false,
"credits_available": 9450,
"credit_cost_per_lead": 1,
"max_affordable_leads": 9450,
"max_credits_per_file": 500,
"billing": { "charged_for": ["hot", "warm"], "credits_per_qualified_lead": 1, "max_credits_per_file": 500, "rule": "…" },
"batches_this_month": 12,
"org_credits": 0,
"is_admin": false
}
credit_cost_per_lead is per Hot or Warm lead, not per row;
max_affordable_leads is how many Hot/Warm leads your balance pays for.
Billing & Subscription API
Start a Stripe checkout for a plan. Open the returned
checkout_url in a browser to pay.
{ "plan_id": "starter", "billing_period": "monthly" }
{ "checkout_url": "https://checkout.stripe.com/...", "session_id": "cs_..." }
plan_id: starter, scale or enterprise;
billing_period: monthly or annual. Plans and prices:
GET /api/billing/plans.
Get current subscription details and credit balance.
{
"plan_id": "starter",
"plan_name": "Starter",
"status": "active",
"credits_remaining": 1840,
"credits_included": 2500,
"price": 399,
"currency": "usd",
"is_trial": false,
"trial_ends_at": null,
"renews_at": "2026-11-01T00:00:00+00:00",
"next_billing_at": "2026-11-01T00:00:00+00:00",
"billing_period": "monthly",
"topup_price_per_lead": 0.2,
"lgaas_plan": null,
"stripe_subscription_id": "sub_..."
}
Your credit history: purchases, plan credits and batch charges, newest first (at most 200).
[
{
"id": 812,
"user_id": "user_17283",
"amount": -142.0,
"balance_after": 1840.0,
"type": "debit",
"description": "[Batch] Batch batch_a1b2c3d4_1760259600: 142 qualified leads",
"reference_id": "batch_a1b2c3d4_1760259600",
"created_at": "2026-10-12T11:04:37"
}
]
List all invoices for the current user.
{
"invoices": [
{
"invoice_number": "A1B2C3D4-0001",
"invoice_date": "2026-10-01T10:00:00+00:00",
"total_amount": 399.0,
"currency": "USD",
"status": "paid",
"description": "Starter",
"pdf_available": true,
"hosted_invoice_url": "https://invoice.stripe.com/..."
}
]
}
Download invoice PDF.
Limits
- Files: up to 500 MB, 1,000,000 rows and 200 columns — the same on every plan.
- Billing: 1 credit per Hot or Warm lead, at most 500 credits per file, never more than your balance. Cold leads and rows left out of the results are free.
- Queue: Enterprise batches run on a priority queue; every other plan shares the standard queue.
- Requests: there are no per-plan request limits. Some endpoints limit requests per IP address,
e.g.
POST /api/uploadto 20 a minute; over a limit the API answers429— wait a minute and retry.
GET /api/limits returns your plan's
numbers; POST /api/estimate the cost range of a file.
Webhook Events
Add your endpoint under Webhooks in the dashboard sidebar (or with
POST /api/webhooks). When an event happens, Leedrush sends a JSON POST to it.
The URL must be a public http(s) address. Answer within 10 seconds with any 2xx.
| Event | Trigger |
|---|---|
batch.completed |
A CSV batch finished: row and qualified (hot + warm) counts and a link to the results |
lead.qualified |
LGaaS qualified a lead for you (inbound qualification or Autopilot delivery) |
| Header | Value |
|---|---|
X-Leedrush-Event |
The event name, e.g. batch.completed |
X-Leedrush-Event-Id |
Unique per event and the same on every retry — use it to skip duplicates |
X-Leedrush-Attempt |
1 for the first delivery, 2–4 for retries |
X-Leedrush-Signature |
Only when the webhook has a secret: HMAC-SHA256 of the raw body, keyed with the secret (hex) |
Retries. If your endpoint times out, cannot be reached or answers 5xx or
429, Leedrush retries up to 3 more times: 1 minute, 5 minutes and 30 minutes after the
previous attempt. Only the endpoint that failed is retried. Other 4xx answers are not
retried. The body, and so the signature, is identical on every attempt. "Send test" in the dashboard
is a single attempt. Every attempt is in the delivery log with its number.
{
"batch_id": "batch_a1b2c3d4",
"filename": "leads.csv",
"total_leads": 500,
"qualified_leads": 142,
"yield_rate": 0.284,
"completed_at": "2026-10-12T11:00:00+00:00",
"results_url": "/api/batches/batch_a1b2c3d4/export"
}
import hmac, hashlib
from fastapi import Request, HTTPException
WEBHOOK_SECRET = "your_webhook_secret"
seen_event_ids = set() # keep these in your database
async def handle_webhook(request: Request):
payload = await request.body()
sig = request.headers.get("X-Leedrush-Signature", "")
expected = hmac.new(
WEBHOOK_SECRET.encode(), payload, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, sig):
raise HTTPException(status_code=401, detail="Invalid signature")
event_id = request.headers["X-Leedrush-Event-Id"]
if event_id in seen_event_ids: # a retry of an event you already handled
return {"ok": True}
seen_event_ids.add(event_id)
if request.headers["X-Leedrush-Event"] == "batch.completed":
event = await request.json()
batch_id = event["batch_id"]
# your logic here
return {"ok": True} # 2xx = delivered
Error Reference
All errors return
JSON with a detail field. Use the HTTP status code to determine the cause.
| Code | Meaning | Common Cause |
|---|---|---|
200 |
OK | Request succeeded |
400 |
Bad Request | The file failed its checks (type, size, rows, columns), or a field is invalid |
401 |
Unauthorized | Missing or invalid Bearer token — re-authenticate |
402 |
Payment Required | No credits left — top up, then upload or process again |
403 |
Forbidden | The resource belongs to another account, or the endpoint is for admins |
404 |
Not Found | Batch ID or resource does not exist |
410 |
Gone | An endpoint or integration that is no longer offered |
413 |
Payload Too Large | Upload far over the 500 MB file limit (refused before it reaches the API) |
422 |
Unprocessable | The request body or parameters don't match the endpoint — detail lists the fields |
429 |
Rate Limited | Too many requests from your IP to a rate-limited endpoint — wait a minute and retry |
500 |
Server Error | Unexpected server error — contact support@leedrush.com |
Inbound Qualification Agent
Qualify any lead against your ICP in real time with Leedrush's AI (DeepSeek). Requires your personal inbound webhook key (visible in the LGaaS → Inbound tab).
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/lgaas/inbound/qualify?api_key=<key> | Qualify a single lead — returns score, verdict, reason |
| GET | /api/lgaas/inbound/webhook-url | Get your personal inbound webhook URL |
| GET | /api/lgaas/inbound/leads | List inbound leads with qualification results |
| GET | /api/lgaas/inbound/stats | Qualified vs rejected counts |
curl -X POST "https://leedrush.com/api/lgaas/inbound/qualify?api_key=ldr_inb_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "cto@acme.com",
"first_name": "Alice",
"last_name": "Smith",
"company": "Acme Corp",
"title": "CTO",
"industry": "SaaS"
}'
{
"email": "cto@acme.com",
"qualified": true,
"confidence": 0.87,
"priority": "high",
"reasons": ["CTO at a SaaS company matches the ICP"],
"disqualifiers": [],
"icp_name": "SaaS CTOs"
}
Webhook Subscriptions
Create outbound webhook endpoints that fire on batch.completed or lead.qualified events. Requires session auth (JWT).
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/webhooks | List your webhook destinations with delivery stats |
| POST | /api/webhooks | Create a webhook endpoint |
| PATCH | /api/webhooks/{id} | Update name, URL or active status |
| DELETE | /api/webhooks/{id} | Remove a webhook |
| GET | /api/webhooks/{id}/logs | Delivery log: every attempt with its number and event id (latest 30; ?limit=) |
| POST | /api/webhooks/{id}/test | Send an example event once (no retries) and return the response |
| GET | /api/webhooks/events | List all supported event types |
curl -X POST https://leedrush.com/api/webhooks \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "HubSpot Notify",
"url": "https://hooks.example.com/lead-qualified",
"event_type": "lead.qualified",
"secret_token": "my_signing_secret"
}'
When a matching event fires, Leedrush sends a POST to your URL with the event payload. If secret_token is set, the request includes an X-Leedrush-Signature header (HMAC-SHA256 of the request body). Timeouts, network errors, 5xx and 429 are retried up to 3 times (1, 5 and 30 minutes) with the same X-Leedrush-Event-Id — see Webhook Events above.
CRM Bidirectional Sync
Trigger a pull-validate-push cycle for a connected CRM (HubSpot, Pipedrive). Contacts are pulled, their emails checked (mails.so), and the results written back. Runs as an async Celery task.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/integrations/{id}/sync | Start a sync — returns task_id |
| GET | /api/integrations/{id}/sync-status | Last sync log entry (records_count, status, error) |
| POST | /api/integrations/{id}/test | Test credentials (live ping to the CRM) |
| POST | /api/integrations/{id}/push | Push leads from a batch to the CRM |
| GET | /api/integrations/marketplace | List all available integration providers |
# Start sync
curl -X POST https://leedrush.com/api/integrations/integ_abc123/sync \
-H "Authorization: Bearer YOUR_JWT"
# Returns: {"task_id": "celery-uuid-...", "status": "syncing"}
# Poll for result (check after a few seconds)
curl https://leedrush.com/api/integrations/integ_abc123/sync-status \
-H "Authorization: Bearer YOUR_JWT"
# Returns:
{
"last_sync": {
"direction": "pull_and_push",
"records_count": 247,
"status": "ok",
"created_at": "2026-07-06T03:00:00"
}
}