Back to API overview

Leads

Live

Inbound and outbound lead records. Each lead is bound to the calling organization (RLS enforced). The shape below matches the live Postgres schema; additional fields available on the single-record GET include AI-qualification metadata (pain_points, timeline, budget_range, ai_reasoning, qualification_completed_at).

GET/api/v1/leadsreadLive

List leads

Cursor-paginated. Default limit 50, max 200. Filter by pipeline_stage, source, and created_at range (?start_at=ISO8601&end_at=ISO8601). Cursors are opaque base64url-encoded JSON.

Example response
{
  "data": [
    {
      "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
      "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
      "name": "Sarah Mitchell",
      "company": null,
      "email": "sarah.mitchell@example.com",
      "source": "google_ads",
      "lead_score": 87,
      "pipeline_stage": "qualified",
      "industry": null,
      "company_size": null,
      "created_at": "2026-04-27T13:22:05.114Z",
      "updated_at": "2026-04-27T13:22:09.218Z"
    }
  ],
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0yN1QxMzoyMjowNS4xMTRaIiwiaWQiOiI5ZjFjOGEzMi00ZDY1LTRlMmItOGE5MS0zYzBlN2Q0YjJmMTgifQ"
}
GET/api/v1/leads/{id}readLive

Fetch a lead

Tenant-scoped: returns 404 if the lead belongs to a different organization. Returns the full record including AI-qualification metadata.

Example response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "name": "Sarah Mitchell",
  "company": null,
  "email": "sarah.mitchell@example.com",
  "source": "google_ads",
  "lead_score": 87,
  "pipeline_stage": "qualified",
  "industry": "residential",
  "company_size": null,
  "pain_points": "Recurring basement moisture after spring rain",
  "timeline": "this_week",
  "budget_range": null,
  "ai_reasoning": "High urgency: water damage signal, recent storm event, in-radius",
  "qualification_completed_at": "2026-04-27T13:22:09.218Z",
  "created_at": "2026-04-27T13:22:05.114Z",
  "updated_at": "2026-04-27T13:22:09.218Z"
}
POST/api/v1/leadswriteLive

Create a lead

Creates a new lead. Validates email format if provided. At least one of email, or (name + company), is required. Returns 201 with the created record.

Example request body
{
  "name": "Sarah Mitchell",
  "email": "sarah.mitchell@example.com",
  "source": "website",
  "industry": "residential",
  "pipeline_stage": "new"
}
Example response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "name": "Sarah Mitchell",
  "company": null,
  "email": "sarah.mitchell@example.com",
  "source": "website",
  "lead_score": 0,
  "pipeline_stage": "new",
  "industry": "residential",
  "company_size": null,
  "created_at": "2026-04-27T13:22:05.114Z",
  "updated_at": "2026-04-27T13:22:05.114Z"
}
PATCH/api/v1/leads/{id}writeLive

Update a lead

Partial update. Whitelisted fields only: name, company, email, source, lead_score (0โ€“100), pipeline_stage, industry, company_size, pain_points, timeline, budget_range, ai_reasoning. Every change is recorded in /audit-log as a `lead.updated` event with a per-field diff.

Example request body
{
  "pipeline_stage": "won",
  "lead_score": 92
}
Example response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "name": "Sarah Mitchell",
  "company": null,
  "email": "sarah.mitchell@example.com",
  "source": "google_ads",
  "lead_score": 92,
  "pipeline_stage": "won",
  "updated_at": "2026-04-27T18:55:42.001Z"
}
POST/api/v1/leads/{id}/scorewriteRoadmap

Re-score a lead

Force a re-score using the latest signals. Returns the new score and explanation.

Example response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "lead_score": 91,
  "ai_reasoning": "Recent storm event in service radius (+0.18); phone validated (+0.10); fast form completion (+0.08)",
  "scored_at": "2026-04-27T19:01:15.214Z"
}
POST/api/v1/leads/{id}/push-to-crmwriteRoadmap

Push a lead to the configured CRM

Manually trigger a CRM push if the automatic push failed or was skipped. Idempotent: re-pushing returns the existing external_id without creating a duplicate record.

Example response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "crm_sync": {
    "vendor": "jobnimbus",
    "external_id": "jn_117422",
    "status": "ok",
    "last_synced_at": "2026-04-27T19:03:42.118Z"
  }
}