Leads
Base path:
/api/v1/{workspace_id}/leads/
List & create
GET /api/v1/{workspace_id}/leads/
POST /api/v1/{workspace_id}/leads/
Authorization: Bearer <token>
Content-Type: application/json
Create lead — request body
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@acme.com",
"company": "Acme Corp",
"job_title": "VP Marketing",
"phone": "+1-555-0100",
"source": "manual",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"assigned_to_id": "660e8400-e29b-41d4-a716-446655440001",
"status": "new"
}
Optional nested demo_booking (single create only — not bulk OCR/import). Omit it for normal lead/contact create (web). When present, schedule validation and overlap checks run before the lead is created; on success the booking is linked to the new or recaptured lead. Lead source is preserved (not overwritten to demo_booking).
Bulk OCR / quick-save: pass the same demo_booking object inside each presign files[] entry (stored on the scan item capture_fields). After the lead is created/recaptured, the booking is attached when possible; overlap failures skip the demo but still keep the lead.
{
"email": "jane@acme.com",
"first_name": "Jane",
"last_name": "Doe",
"company": "Acme Corp",
"event": "550e8400-e29b-41d4-a716-446655440000",
"source": "manual",
"demo_booking": {
"start_time": "2026-08-20T15:00:00Z",
"end_time": "2026-08-20T15:30:00Z",
"timezone": "Asia/Kolkata",
"mode": "virtual",
"status": "scheduled",
"notes": "Booth follow-up",
"rep_id": "660e8400-e29b-41d4-a716-446655440001"
}
}
lead_name, lead_email, company, person, and workspace_event are filled from the lead when omitted. Requires demo create permission in addition to lead create.
Create lead — response (201 Created)
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"slug": "abc123def456",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@acme.com",
"company": "Acme Corp",
"source": "manual",
"status": "new",
"lead_score": null,
"assigned_to": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"email": "rep@example.com",
"name": "Sales Rep"
},
"event": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "SaaStr Annual 2026"
},
"captured_at": "2026-06-20T14:30:00Z"
}
When demo_booking was sent, the response also includes a summary:
{
"demo_booking": {
"id": "dd0e8400-e29b-41d4-a716-446655440008",
"start_time": "2026-08-20T15:00:00+00:00",
"end_time": "2026-08-20T15:30:00+00:00",
"timezone": "Asia/Kolkata",
"status": "scheduled",
"mode": "virtual",
"source": "manual",
"meeting_link": "",
"notes": "Booth follow-up",
"rep_id": null,
"workspace_event_id": "550e8400-e29b-41d4-a716-446655440000"
},
"demo_booking_id": "dd0e8400-e29b-41d4-a716-446655440008"
}
Invalid schedule → 400. Time overlap → 409 with code: demo_booking_time_overlap (lead is not created). Missing demo create permission → 403.
List leads — response (200 OK)
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@acme.com",
"company": "Acme Corp",
"status": "new",
"source": "manual"
}
]
}
Lead detail
GET /api/v1/{workspace_id}/leads/{lead_id}/
PUT /api/v1/{workspace_id}/leads/{lead_id}/
PATCH /api/v1/{workspace_id}/leads/{lead_id}/
DELETE /api/v1/{workspace_id}/leads/{lead_id}/
Note: {lead_id} is a string identifier.
Update lead — request body (PATCH)
{
"status": "qualified",
"lead_score": 85,
"assigned_to_id": "660e8400-e29b-41d4-a716-446655440001",
"notes": "Requested enterprise pricing"
}
OCR (business card / badge scan)
POST /api/v1/{workspace_id}/leads/ocr/
POST /api/v1/{workspace_id}/leads/events/{event_id}/leads/ocr/
Extract-only (multipart image). Create the lead separately via POST …/leads/ with source: "ocr"; optional multipart card_image attaches the scan to lead assets.
Quick lead (scan + save)
One multipart request: OCR the card and create or recapture the lead immediately. Same JPEG/PNG, 5 MB limit as extract-only. Same capture fields as bulk OCR. Image is sent with the request (not a presigned URL).
POST /api/v1/{workspace_id}/leads/quick-lead/
POST /api/v1/{workspace_id}/leads/events/{event_id}/leads/quick-lead/
Multipart fields:
imageorfile— JPEG/PNG, max 5 MB (required)event_id— optional if not in the URLenrich— defaultfalse(same as bulk OCR)- Capture fields (optional, same keys as bulk OCR
files[]):notes,event_interest_level,interested_in,decision_role,decision_maker,purchase_timeline,deal_budget_range,company_size,customer_type,meeting_intent,preferred_contact_method,next_action,detailed_next_action,follow_up_reminder,next_follow_up_date,consent_given,assigned_to_id demo_booking— optional JSON string ({"start_time","end_time","mode"?})
interested_in and demo_booking may be JSON strings because this is multipart, not JSON.
Response 201 when created, 200 when recaptured:
{
"outcome": "created",
"lead": {},
"extracted": {},
"confidence": 0.91,
"method": "vision",
"needs_email": false,
"attachment_id": "…"
}
outcome is created or recaptured. needs_email is true for provisional (no email, but name/company/phone). Compress large camera photos on device so they stay under 5 MB.
Use bulk OCR (presign) for many cards. Keep POST …/leads/ocr/ when the user should edit fields before save.
Bulk OCR (create leads)
Preferred flow — presigned URLs (parallel direct-to-S3 uploads):
POST /api/v1/{workspace_id}/leads/bulk-ocr/presign/
POST /api/v1/{workspace_id}/leads/events/{event_id}/leads/bulk-ocr/presign/
POST /api/v1/{workspace_id}/leads/bulk-ocr/{batch_id}/start/
GET /api/v1/{workspace_id}/leads/bulk-ocr/{batch_id}/
POST …/presign/with JSON{ files: [{filename, content_type, file_size, notes?, event_interest_level?, …}], event_id?, enrich? }→201withbatch+uploads[](upload_url,s3_key,content_type,item_id).- Client
PUTeach file toupload_urlwith matchingContent-Type(parallel OK). POST …/{batch_id}/start/verifies objects in S3 and enqueues OCR →202.- Poll
GET …/{batch_id}/for per-item status, lead ids, and attachment ids.
Optional per-file capture fields on files[] (same keys as mobile lead create; applied when the lead is created/recaptured):
notes— free text (max 5000 chars)event_interest_level—cold|warm|hot(aliasinterest_level; legacylow/medium/highmap to cold/warm/hot)interested_in— string or array of slugsdecision_role—decision_maker|influencer|evaluator|end_user(also acceptsdecision_maker: true)purchase_timeline,deal_budget_range,company_size,customer_type,meeting_intentpreferred_contact_method,next_action,detailed_next_action,follow_up_remindernext_follow_up_date— ISO date (YYYY-MM-DD) or datetime stringconsent_given— booleanassigned_to_id— workspace member user id or membership id (same as lead create; stored as user id)
On create, provided fields are set on the lead. On recapture: notes append; choice/scalars fill empty only; interested_in unions; consent only promotes to true; assignee fill-empty only. Extra fields are also returned on each batch item as capture_fields.
When the batch reaches a terminal status (completed / partial / failed), the user who started the scan receives a summary in-app notification (and email if Leads email is enabled under Settings → Notifications). Item failures do not stop other items.
JPEG/PNG only, max 50 images, 5 MB each. enrich defaults to false.
When OCR finds no email but has usable details (name, company, phone, or LinkedIn), a provisional lead is still created (raw_payload.needs_email=true), the card image is attached, and full enrichment is queued so Apollo can discover an email. If enrichment finds an email that already exists in the workspace, the provisional lead is merged into the existing one. Cards with no email and no other identity hints are still skipped. Manual POST …/leads/ still requires email.
OCR stores primary email / phone on the lead (identity), plus additional_emails / additional_phones (JSON), and address / city / state / country when readable. Slash-grouped numbers such as +91 98765 43210 / 11 / 12 or +91 98765 43210 / +91 90000 00002 are expanded; landlines are typed separately from mobiles.
Legacy multipart (small batches / fallback):
POST /api/v1/{workspace_id}/leads/bulk-ocr/
POST /api/v1/{workspace_id}/leads/events/{event_id}/leads/bulk-ocr/
Multipart: one or more images. Optional event_id, enrich. Returns 202 with batch id.
Enrichment
POST /api/v1/{workspace_id}/leads/enrich/
Bulk operations
POST /api/v1/{workspace_id}/leads/bulk-delete/
GET /api/v1/{workspace_id}/leads/export/
POST /api/v1/{workspace_id}/leads/bulk-import/preview/
POST /api/v1/{workspace_id}/leads/bulk-import/
Related records
GET /api/v1/{workspace_id}/leads/{lead_id}/tasks-list/
GET /api/v1/{workspace_id}/leads/{lead_id}/demo-bookings/
GET /api/v1/{workspace_id}/leads/{lead_id}/activity-log/
Person identity
GET /api/v1/{workspace_id}/leads/persons/{person_id}/
GET /api/v1/{workspace_id}/leads/persons/{person_id}/history/
Demo bookings (nested)
POST /api/v1/{workspace_id}/leads/bookings/
GET /api/v1/{workspace_id}/leads/demo-bookings/
POST /api/v1/{workspace_id}/leads/demo-bookings/create/
GET /api/v1/{workspace_id}/leads/demo-bookings/{booking_id}/
PUT /api/v1/{workspace_id}/leads/demo-bookings/{booking_id}/update/
DELETE /api/v1/{workspace_id}/leads/demo-bookings/{booking_id}/delete/
Also see Demo bookings for the global mount.
Lead statuses
GET /api/v1/{workspace_id}/lead-statuses/
POST /api/v1/{workspace_id}/lead-statuses/
GET /api/v1/{workspace_id}/lead-statuses/{status_id}/
PUT /api/v1/{workspace_id}/lead-statuses/{status_id}/
PATCH /api/v1/{workspace_id}/lead-statuses/{status_id}/
DELETE /api/v1/{workspace_id}/lead-statuses/{status_id}/
POST /api/v1/{workspace_id}/lead-statuses/{status_id}/activate/
POST /api/v1/{workspace_id}/lead-statuses/{status_id}/deactivate/
POST /api/v1/{workspace_id}/lead-statuses/seed/
Webhooks
lead.createdlead.updated/lead.status_changed/lead.assigned
See Webhooks.