API Documentation

Integrate qualified revenue opportunities directly into your stack.

Data Pipeline Overview

Every row of an uploaded file goes through these steps:

01 Ingest

CSV, Excel or JSON

02 Normalize

Standardize

03 Validate

Email + Phone

04 Enrich

Website + AI

05 Dedup

Fuzzy Match

06 Score

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.

API key
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

POST /api/upload

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
curl -s -X POST https://leedrush.com/api/upload \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@leads.csv" \
  | jq .
Response
{
  "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.

POST /api/estimate

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
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 .
Response
{
  "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.

POST /api/process

Start a background processing job for an uploaded file.

curl
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 .
Response
{
  "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.

GET /api/batches

Retrieve a list of all batch jobs and their status.

curl
curl -s "https://leedrush.com/api/batches?limit=20" \
  -H "Authorization: Bearer $TOKEN" \
  | jq .
Response
{
  "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.

Polling for Batch Status
Poll until finished
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).

GET /api/batches/{id}/export

Download the results of a batch: CSV by default, ?format=json for JSON.

curl
curl -s -o results.csv "https://leedrush.com/api/batches/$BATCH_ID/export" \
  -H "Authorization: Bearer $TOKEN"
Columns
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.

GET /api/limits

Your plan, its file limits, your credit balance and the billing rule.

Response
{
  "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

POST /api/billing/create-checkout

Start a Stripe checkout for a plan. Open the returned checkout_url in a browser to pay.

Request Body
{ "plan_id": "starter", "billing_period": "monthly" }
Response
{ "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 /api/billing/subscription

Get current subscription details and credit balance.

Response
{
  "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_..."
}
GET /api/billing/transactions?limit=50

Your credit history: purchases, plan credits and batch charges, newest first (at most 200).

Response
[
  {
    "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"
  }
]
GET /api/billing/invoices

List all invoices for the current user.

Response
{
  "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/..."
    }
  ]
}
GET /api/billing/invoices/{invoice_number}/pdf

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/upload to 20 a minute; over a limit the API answers 429 — 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.completed payload
{
  "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"
}
Python — receive, verify & de-duplicate
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-urlGet your personal inbound webhook URL
GET/api/lgaas/inbound/leadsList inbound leads with qualification results
GET/api/lgaas/inbound/statsQualified vs rejected counts
cURL — qualify a lead
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"
  }'
Response
{
  "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/webhooksList your webhook destinations with delivery stats
POST/api/webhooksCreate a webhook endpoint
PATCH/api/webhooks/{id}Update name, URL or active status
DELETE/api/webhooks/{id}Remove a webhook
GET/api/webhooks/{id}/logsDelivery log: every attempt with its number and event id (latest 30; ?limit=)
POST/api/webhooks/{id}/testSend an example event once (no retries) and return the response
GET/api/webhooks/eventsList all supported event types
cURL — create webhook
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}/syncStart a sync — returns task_id
GET/api/integrations/{id}/sync-statusLast sync log entry (records_count, status, error)
POST/api/integrations/{id}/testTest credentials (live ping to the CRM)
POST/api/integrations/{id}/pushPush leads from a batch to the CRM
GET/api/integrations/marketplaceList all available integration providers
cURL — trigger sync + poll status
# 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"
  }
}