Read a lead

GET /api/public/leads/:id — poll for status, transcript, and recording.

GET /api/public/leads/:id
Authorization: Bearer ck_live_...

Reads the current state of a single lead. Always scoped to the workspace owning the API key — a leaked key from Workspace A can never read Workspace B's leads.

Use this for

  • Polling for status changes from your CRM.
  • Pulling the transcript / recording URL once the call ends (voice workspaces).
  • Showing booking confirmation status in your own UI.

Webhooks would be a better fit if you have a lot of leads — they're on the roadmap but not shipped yet.

Responses

200 OK

{
  "ok": true,
  "lead": {
    "id": "f54466f4-afc3-4e27-84f9-a3be6fcac978",
    "channel": "voice",
    "name": "Jane Doe",
    "phone": "+447123456789",
    "email": "jane@acme.com",
    "goal": "Looking to migrate from a competitor",
    "status": "booked",
    "qualificationNotes": null,
    "bookingUid": "xshQhAccwdqJKxTDKFAUNi",
    "bookingStart": "2026-06-08T13:00:00.000Z",
    "metadata": {
      "companyName": "Acme Corp",
      "industry": "B2B SaaS"
    },
    "createdAt": "2026-06-07T13:03:37.162Z",
    "updatedAt": "2026-06-07T13:09:11.444Z"
  },
  "voiceCall": {
    "status": "ended",
    "startedAt": "2026-06-07T13:03:42.000Z",
    "endedAt": "2026-06-07T13:09:05.000Z",
    "durationSeconds": 323,
    "summary": "Jane confirmed she's evaluating us to replace Competitor X. Budget is ~£500/mo. Booked a consultation for Monday at 2pm.",
    "transcript": "Assistant: Hi Jane, this is Riley from ...\n\nLead: Yeah, hi ...\n\n...",
    "recordingUrl": "https://storage.vapi.ai/.../019ea22e-80ca-7001-95fb-4932d5c048f2.wav",
    "endedReason": "customer-ended-call"
  }
}

The voiceCall field is null for WhatsApp workspaces. For voice workspaces, it carries the latest call_session row.

Lead status values

statusMeans
newJust arrived. Agent hasn't reached out yet.
in_conversationAgent has reached out and is qualifying.
bookedQualified + booked onto Cal.com. bookingUid + bookingStart populated.
unqualifiedAgent decided the lead isn't a fit. Reason in qualificationNotes.
needs_humanEither the agent escalated, or an operator clicked "Take over" in the dashboard.

404 Not Found

{ "ok": false, "error": "Lead not found." }

Returned when:

  • The lead ID doesn't exist.
  • The lead belongs to a different workspace from your API key.

(We deliberately don't distinguish the two — leaking the existence of leads across workspaces would be a security regression.)

401 Unauthorized

See Authentication.

Polling strategy

If you're using polling instead of webhooks:

  • Poll every 10–30 seconds while the lead is new or in_conversation.
  • Stop polling once status reaches a terminal state (booked, unqualified, needs_human) — those won't change without operator action.
  • For voice workspaces specifically, also stop polling when voiceCall.status === "ended" — the call won't restart.

Don't poll faster than every 5 seconds; you'll burn through the hourly rate limit fast.