Objects
The lead object
Section titled “The lead object”What every lead endpoint answers with, and what lead events carry.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string (uuid) | yes | |
external_id |
string or null | yes | Your own ID for the lead. |
campaign_id |
string (uuid) | yes | |
name |
string or null | yes | |
phone |
string | yes | +1 and 10 digits. |
email |
string or null | yes | |
address |
string or null | yes | |
fields |
object | yes | Your custom fields. |
consent |
Consent or null | yes | |
consent.source |
string | yes | Where the lead agreed, for example web_form, crm or purchased_list. At most 100 characters. |
consent.agreed_at |
string (date-time) | yes | When the lead agreed. |
consent.text |
string | The words the lead agreed to. Send text or url. At most 5000 characters. | |
consent.url |
string (uri) | The page the lead agreed on. Send text or url. | |
consent.ip |
string | The lead’s IP address when agreeing, if you have it. | |
lead_source |
string or null | yes | |
owner |
object or null | yes | The SDR the latest call was dialed for, or null. |
owner.id |
string | yes | |
owner.name |
string | yes | |
created |
string (date-time) | yes | |
updated |
string (date-time) | yes | |
status |
string | yes | Where the lead is now. See the Speed to lead guide for each word. One of new / queued / scheduled / held / dialing / on_call / callback_scheduled / done / out_of_queue. |
call_now |
boolean | yes | |
place_in_line |
integer or null | yes | 1 = next, 0 = being dialed now. Only on a single-lead read. |
estimated_wait_seconds |
integer or null | yes | A guess, in seconds. |
scheduled_for |
string (date-time) or null | yes | status scheduled: when the lead can be called. |
scheduled_reason |
string or null | yes | One of outside_calling_hours / recent_contact_cooldown / retry_after_outcome / next_dial_time. |
held_reason |
string or null | yes | One of campaign_paused / campaign_not_started / spend_cap_reached. |
callback_at |
string (date-time) or null | yes | status callback_scheduled: the booked time (UTC). |
late |
boolean | yes | A call_now lead that waited past the late limit (15 minutes by default). |
attempts |
integer | yes | |
last_outcome |
string or null | yes | A stable outcome word (see the Outcomes guide). null while there is no outcome yet. One of interested / not_interested / callback / voicemail / no_answer / busy / failed / dnc / wrong_number / hung_up / screened / language_barrier / not_qualified / undetermined / other. |
last_outcome_label |
string or null | yes | |
next_dial_at |
string (date-time) or null | yes | |
calls |
array of LeadCallSummary | yes | The last 10 calls, newest first. |
livemode |
boolean | On every API reply. A lead inside a live event leaves it out. |
{ "id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "external_id": "crm-10442", "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "name": "LEAD_NAME", "phone": "+15555550142", "email": "lead@example.com", "address": "1 Example Street, Springfield", "fields": { "roof_age": "12" }, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" }, "lead_source": "Example CRM", "owner": null, "created": "2026-10-06T16:58:04.000Z", "updated": "2026-10-06T16:58:04.000Z", "status": "queued", "call_now": true, "place_in_line": 3, "estimated_wait_seconds": 90, "scheduled_for": null, "scheduled_reason": null, "held_reason": null, "callback_at": null, "late": false, "attempts": 0, "last_outcome": null, "last_outcome_label": null, "next_dial_at": null, "calls": [], "livemode": true}The call object
Section titled “The call object”What every call endpoint answers with, and what call events carry.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string (uuid) | ||
object |
string | One of call. |
|
livemode |
boolean | ||
lead |
object or null | ||
lead.id |
string (uuid) | ||
lead.external_id |
string or null | ||
lead.name |
string or null | ||
lead.phone |
string or null | E.164 (+1…) | |
campaign_id |
string (uuid) or null | ||
direction |
string | One of outbound / inbound. |
|
status |
string | One of ringing / in_progress / ended. |
|
outcome |
string or null | A stable word. null while the call has no outcome yet; other only for a label we don’t know (see outcome_label). One of interested / not_interested / callback / voicemail / no_answer / busy / failed / dnc / wrong_number / hung_up / screened / language_barrier / not_qualified / undetermined / other. |
|
outcome_label |
string or null | Your organization’s name for the outcome, e.g. Hung Up | |
note |
string or null | The AI’s after-call note, or what a person wrote | |
duration_seconds |
integer or null | ||
started |
string (date-time) | ||
answered |
string (date-time) or null | ||
ended |
string (date-time) or null | ||
updated |
string (date-time) | ||
handled_by |
object | ||
handled_by.id |
string or null | The user’s ID; null for the AI and for a closer | |
handled_by.name |
string | ||
handled_by.role |
string | One of sdr / closer / ai. |
|
owner |
object or null | ||
owner.id |
string | ||
owner.name |
string | ||
tth_seconds |
number or null | Seconds from buying intent to a person on the call | |
callback_at |
string (date-time) or null | ||
recording_available |
boolean | ||
transcript_available |
boolean | ||
started_by |
object | ||
started_by.type |
string | One of dialer / api / person. |
|
started_by.api_key_id |
string or null |
{ "id": "e4d3c2b1-a09f-4e8d-9c7b-6a5f4e3d2c1b", "object": "call", "livemode": true, "lead": { "id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "external_id": "crm-10442", "name": "LEAD_NAME", "phone": "+15555550142" }, "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "direction": "outbound", "status": "ended", "outcome": "interested", "outcome_label": "Interested", "note": "Wants a quote for a 6 kW system; home owner, roof replaced 2019.", "duration_seconds": 412, "started": "2026-10-06T16:58:06.000Z", "answered": "2026-10-06T16:58:14.000Z", "ended": "2026-10-06T17:04:58.000Z", "updated": "2026-10-06T17:05:20.000Z", "handled_by": { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "name": "SDR_NAME", "role": "sdr" }, "owner": null, "tth_seconds": 9.3, "callback_at": null, "recording_available": true, "transcript_available": true, "started_by": { "type": "api", "api_key_id": "key_example" }}What POST /leads answers
Section titled “What POST /leads answers”The reply to POST /leads (and each ok result in a batch).
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string (uuid) or null | yes | Our ID for the lead. null on a dry run. |
external_id |
string or null | yes | |
status |
string | yes | One of dialing / queued / scheduled / held. |
call_now |
boolean | yes | |
place_in_line |
integer or null | yes | 1 = next, 0 = being dialed now. |
estimated_wait_seconds |
integer or null | yes | |
scheduled_for |
string (date-time) or null | yes | |
scheduled_reason |
string or null | yes | One of outside_calling_hours / recent_contact_cooldown. |
held_reason |
string or null | yes | One of campaign_paused / campaign_not_started / spend_cap_reached. |
dry_run |
boolean | yes | |
requeued |
boolean | Only when the lead already existed and was put back in the queue. Set to true. |
|
livemode |
boolean | yes | false for a test key: sample data, never real. |
{ "id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "external_id": "crm-10442", "status": "queued", "call_now": true, "place_in_line": 1, "estimated_wait_seconds": 20, "scheduled_for": null, "scheduled_reason": null, "held_reason": null, "dry_run": false, "livemode": true}Consent
Section titled “Consent”What the lead agreed to, if you send it. Stored with the lead and returned.
| Field | Type | Required | Notes |
|---|---|---|---|
source |
string | yes | Where the lead agreed, for example web_form, crm or purchased_list. At most 100 characters. |
agreed_at |
string (date-time) | yes | When the lead agreed. |
text |
string | The words the lead agreed to. Send text or url. At most 5000 characters. | |
url |
string (uri) | The page the lead agreed on. Send text or url. | |
ip |
string | The lead’s IP address when agreeing, if you have it. |
{ "source": "string", "agreed_at": "2026-10-06T17:05:20Z", "text": "string", "url": "https://example.com", "ip": "string"}The event envelope
Section titled “The event envelope”Every event, from GET /events or a webhook.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | Unique. If you have seen this id before, skip it. |
type |
string | yes | One of lead.created / lead.queued / lead.interested / lead.opted_out / lead.late / call.started / call.answered / call.completed / handover.accepted / voicemail.left / callback.booked. |
created |
string (date-time) | yes | |
livemode |
boolean | yes | false for a test key: sample data, never real. |
api_version |
string | yes | |
data |
object | yes | |
data.object |
object | yes | The lead or the call, as GET /leads/{id} or GET /calls/{id} returns it, plus the event’s own fields. |
sample |
boolean | Only on a “Send test event” sample. Set to true. |
{ "id": "string", "type": "lead.created", "created": "2026-10-06T17:05:20Z", "livemode": true, "api_version": "2026-10-01", "data": { "object": {} }, "sample": true}The webhook endpoint object
Section titled “The webhook endpoint object”Where events go.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string (uuid) | yes | |
object |
string | yes | Set to "webhook_endpoint". |
url |
string (uri) | yes | |
events |
array of string | yes | |
campaign_ids |
array of string (uuid) or null | yes | null = every campaign. |
mode |
string | yes | One of live / test. |
livemode |
boolean | yes | false for a test key: sample data, never real. |
status |
string | yes | paused: by you, or by us after 24 hours of failures in a row. One of active / paused. |
failing_since |
string (date-time) or null | yes | |
paused_at |
string (date-time) or null | yes | |
last_success_at |
string (date-time) or null | yes | |
previous_secret_expires_at |
string (date-time) or null | yes | During a secret roll: when the old secret stops signing. |
created |
string (date-time) | yes | |
updated |
string (date-time) | yes |
{ "id": "3f6a9c2e-1b7d-4e5f-8a90-1c2d3e4f5a6b", "object": "webhook_endpoint", "url": "https://crm.example.com/callview", "events": [ "call.completed", "lead.interested" ], "campaign_ids": null, "mode": "live", "livemode": true, "status": "active", "failing_since": null, "paused_at": null, "last_success_at": "2026-10-06T17:05:21.000Z", "previous_secret_expires_at": null, "created": "2026-10-01T09:00:00.000Z", "updated": "2026-10-01T09:00:00.000Z"}The webhook delivery object
Section titled “The webhook delivery object”One event sent (or waiting) to one endpoint.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | |
object |
string | yes | Set to "webhook_delivery". |
endpoint_id |
string (uuid) | yes | |
event_id |
string or null | yes | |
event_type |
string | yes | |
status |
string | yes | One of pending / sent / failed / gave_up / skipped_paused. |
attempts |
integer | yes | Tries so far (7 at most). |
next_attempt_at |
string (date-time) or null | yes | |
last_attempt_at |
string (date-time) or null | yes | |
response_status |
integer or null | yes | |
response_body |
string or null | yes | The first 4 KB of your answer. |
duration_ms |
integer or null | yes | |
resent_from |
string or null | yes | |
created |
string (date-time) | yes | |
livemode |
boolean | yes | false for a test key: sample data, never real. |
{ "id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a", "object": "webhook_delivery", "endpoint_id": "3f6a9c2e-1b7d-4e5f-8a90-1c2d3e4f5a6b", "event_id": "evt_callcompletedxxxxxxxxxxx", "event_type": "call.completed", "status": "sent", "attempts": 1, "next_attempt_at": null, "last_attempt_at": "2026-10-06T17:05:21.000Z", "response_status": 200, "response_body": "ok", "duration_ms": 182, "resent_from": null, "created": "2026-10-06T17:05:20.000Z", "livemode": true}The usage object
Section titled “The usage object”GET /usage.
| Field | Type | Required | Notes |
|---|---|---|---|
object |
string | yes | Set to "usage". |
period |
string | yes | |
timezone |
string | yes | Your organization’s time zone. Months follow it. |
livemode |
boolean | yes | false for a test key: sample data, never real. |
totals |
UsageCounts | yes | |
group_by |
string or null | yes | One of key / campaign / day. |
groups |
array of UsageCounts + object | yes | |
updated |
string (date-time) or null | yes | When the numbers were last counted (every 5 minutes). |
{ "object": "usage", "period": "2026-10", "timezone": "America/New_York", "livemode": true, "totals": { "requests": 1840, "leads_added": 212, "calls": 260, "calls_connected": 141, "minutes": 388, "cost_usd": 58.2 }, "group_by": null, "groups": [], "updated": "2026-10-06T17:05:00.000Z"}The error
Section titled “The error”Every error, from every endpoint. See Error codes.
| Field | Type | Required | Notes |
|---|---|---|---|
error |
object | yes | |
error.type |
string | yes | One of invalid_request_error / authentication_error / permission_error / rate_limit_error / idempotency_error / api_error. |
error.code |
string | yes | A stable word. Branch on this, never on the message. One of invalid_request / invalid_phone / on_dnc_list / outside_calling_hours / duplicate_lead / number_retired / consent_required / campaign_closed / queue_full / test_flows_busy / phone_cannot_change / ambiguous_external_id / duplicate_dnc_entry / not_found / authentication_failed / permission_denied / rate_limited / too_many_concurrent_requests / lead_rate_limited / idempotency_mismatch / idempotency_in_progress / limit_reached / endpoint_paused / service_unavailable / internal_error / lead_busy / lead_taken_out_of_queue / call_limit_reached / number_not_allowed / recording_not_available. |
error.message |
string | yes | For people. Phone numbers and emails in it are masked. |
error.param |
string or null | yes | The field the error is about, when there is one. |
error.request_id |
string or null | yes | The same as the Request-Id header. Quote it when you contact us. |
error.reason |
string | The specific cause when the code is a broader word (for example why authentication failed). | |
error.lead_id |
string (uuid) or null | duplicate_lead only: the lead that already exists. | |
error.missing_permission |
string | permission_denied only: the permission the key is missing, for example “calls:read”. | |
error.campaign_id |
string (uuid) or null | permission_denied only: the campaign outside the key’s scope (null when the endpoint needs a key for every campaign). | |
error.candidates |
array of object | ambiguous_external_id only: the leads that match, so you can add campaign_id. |
{ "error": { "type": "invalid_request_error", "code": "invalid_request", "message": "string", "param": "string", "request_id": "string", "reason": "string", "lead_id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "missing_permission": "string", "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "candidates": [ { "id": "e4d3c2b1-a09f-4e8d-9c7b-6a5f4e3d2c1b", "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01" } ] }}