Leads
Add leads to a campaign (speed to lead), look them up, change them, opt them out.
Every example sends your key ID and secret as CALLVIEW_KEY_ID and CALLVIEW_SECRET (see Keys and authentication). The base address is https://v2-api.callview.ai/api/v1/external.
Add a lead (speed to lead)
Section titled “Add a lead (speed to lead)”POST /leads
Checks the lead first: a valid +1 number, your Do Not Call list, the campaign’s retired numbers, duplicates (by external_id or phone), consent if the campaign asks for it, number rules (no premium or international numbers) and the 5,000 waiting-lead limit. If it passes, the lead goes straight into the campaign’s dial queue.
A call_now lead is dialed before the campaign’s other leads, oldest first, as the campaign’s lines free up. Outside the lead’s calling hours it is scheduled for the next window. A paused or not-started campaign holds the lead and dials it when the campaign runs, and so does a reached spend cap. A refused lead isn’t saved. place_in_line and estimated_wait_seconds are estimates.
With dry_run true every check runs and the lead isn’t saved. Needs leads:write.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
Idempotency-Key |
header | string | Any string you choose (1 to 255 visible characters), new for each new action. Send the same key again within 24 hours and you get the first answer back instead of a second lead or call. At most 255 characters. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
campaign_id |
string (uuid) | yes | |
external_id |
string | Your own ID for the lead. The same external_id twice in a campaign is a duplicate. At most 200 characters. | |
phone |
string | yes | US or Canadian number. +1 and 10 digits is best; (212) 555-7812 is fine too. Numbers in the 555-0100 to 555-0199 range are refused as made up (test mode takes its magic numbers). At most 32 characters. |
name |
string | At most 200 characters. | |
email |
string | At most 254 characters. | |
address |
string | At most 500 characters. | |
fields |
object | Your custom fields, at most 50. The campaign’s script can use them. | |
consent |
Consent | ||
call_now |
boolean | Dial ahead of the campaign’s other leads. Defaults to the campaign’s setting. | |
dry_run |
boolean | Run every check and answer, without saving the lead. |
curl -X POST 'https://v2-api.callview.ai/api/v1/external/leads' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET" \ -H 'Idempotency-Key: crm-10442-postLeads' \ -H 'Content-Type: application/json' \ -d '{ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "email": "[email protected]", "fields": { "roof_age": "12" }, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" }, "call_now": true}'import { randomUUID } from 'node:crypto';
const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/leads', { method: 'POST', headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() }, body: JSON.stringify({ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "fields": { "roof_age": "12" }, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" }, "call_now": true }),});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os, uuid
import requests
res = requests.request( "POST", "https://v2-api.callview.ai/api/v1/external/leads", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "fields": { "roof_age": "12", }, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote", }, "call_now": True, }, timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/leads');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16))], CURLOPT_POSTFIELDS => <<<'JSON'{ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "fields": { "roof_age": "12" }, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" }, "call_now": true}JSON, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 A dry run (not saved), or a lead that already existed and was put back in the queue (requeued true)
{ "data": { "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 }}201 Accepted. status says what happens next.
{ "data": { "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 }}Errors
Add up to 60 leads
Section titled “Add up to 60 leads”POST /leads/batch
Each lead gets the same checks as POST /leads and its own result, in the order you sent them. A lead that fails doesn’t stop the others. campaign_id may be on each lead or once for the batch. Each lead counts toward the key’s new leads a minute. Needs leads:write.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
Idempotency-Key |
header | string | Any string you choose (1 to 255 visible characters), new for each new action. Send the same key again within 24 hours and you get the first answer back instead of a second lead or call. At most 255 characters. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
campaign_id |
string (uuid) | ||
dry_run |
boolean | ||
leads |
array of object | yes | At most 60 items. |
leads[].campaign_id |
string (uuid) | ||
leads[].external_id |
string | At most 200 characters. | |
leads[].phone |
string | yes | At most 32 characters. |
leads[].name |
string | At most 200 characters. | |
leads[].email |
string | At most 254 characters. | |
leads[].address |
string | At most 500 characters. | |
leads[].fields |
object | ||
leads[].consent |
Consent | ||
leads[].call_now |
boolean |
curl -X POST 'https://v2-api.callview.ai/api/v1/external/leads/batch' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET" \ -H 'Idempotency-Key: postLeadsBatch-0001' \ -H 'Content-Type: application/json' \ -d '{ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "name": "Lee Park", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } }, { "external_id": "crm-10444", "phone": "+15550100003", "name": "Sam Ortiz", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } } ]}'import { randomUUID } from 'node:crypto';
const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/leads/batch', { method: 'POST', headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() }, body: JSON.stringify({ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "name": "Lee Park", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } }, { "external_id": "crm-10444", "phone": "+15550100003", "name": "Sam Ortiz", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } } ] }),});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os, uuid
import requests
res = requests.request( "POST", "https://v2-api.callview.ai/api/v1/external/leads/batch", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "name": "Lee Park", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote", }, }, { "external_id": "crm-10444", "phone": "+15550100003", "name": "Sam Ortiz", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote", }, }, ], }, timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/leads/batch');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16))], CURLOPT_POSTFIELDS => <<<'JSON'{ "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "name": "Lee Park", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } }, { "external_id": "crm-10444", "phone": "+15550100003", "name": "Sam Ortiz", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } } ]}JSON, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 A result per lead: { index, ok: true, lead } or { index, ok: false, error: { code, message, param, lead_id } }
{ "data": { "succeeded": 1, "failed": 1, "dry_run": true, "results": [ { "index": 1, "ok": true, "lead": { "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 } } ], "livemode": true }}Errors
| Status | Codes |
|---|---|
| 400 | invalid_request |
| 401 | authentication_failed |
| 403 | permission_denied |
| 404 | not_found |
| 409 | idempotency_mismatch, idempotency_in_progress |
| 429 | lead_rate_limited, rate_limited, too_many_concurrent_requests |
| 500 | internal_error |
| 503 | service_unavailable |
Get a lead - where it is and its calls
Section titled “Get a lead - where it is and its calls”GET /leads/{id}
The lead object: id, external_id, campaign_id, livemode, name, phone, email, address, fields, consent, lead_source, owner ({ id, name } of the SDR its latest call was dialed for, or null), created, updated, status, call_now, place_in_line and estimated_wait_seconds (while queued; estimates), scheduled_for, scheduled_reason, held_reason, callback_at, late, attempts, last_outcome (a stable word, the same as the call object’s outcome) and last_outcome_label (your organization’s name for it), next_dial_at, and calls (the last 10, newest first: { id, started, outcome, outcome_label, duration_seconds }).
status: new (not yet queued), queued (due now), scheduled (waits for scheduled_for: calling hours, a cooldown, a retry), held (held_reason: campaign_paused, campaign_not_started, spend_cap_reached), dialing, on_call, callback_scheduled (callback_at), done (finished, not coming back), out_of_queue (taken out, opted out or on the Do Not Call list).
A lead in a campaign outside the key’s, in another organization, or removed is 404 not_found.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
id |
path | string (uuid) | yes |
curl -X GET 'https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET"const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10', { method: 'GET', headers: { Authorization: `Basic ${auth}` },});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os
import requests
res = requests.request( "GET", "https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 The lead
{ "data": { "id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "external_id": "crm-10442", "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "name": "LEAD_NAME", "phone": "+15555550142", "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 }}Errors
| Status | Codes |
|---|---|
| 400 | invalid_request |
| 401 | authentication_failed |
| 403 | permission_denied |
| 404 | not_found |
| 429 | rate_limited, too_many_concurrent_requests |
| 500 | internal_error |
Find a lead by external_id, or list leads
Section titled “Find a lead by external_id, or list leads”GET /leads
With external_id: the one lead carrying your ID, as { data: lead }.
external_id is unique per campaign, not per organization: a match in
two of the key’s campaigns is 409 ambiguous_external_id with
candidates ([{ id, campaign_id }]); add campaign_id to pick one.
Without it: the key’s leads, newest first, keyset-paged
(starting_after = the last lead’s id), as
{ data: [lead], meta: { next_cursor, has_more } }. With a status
filter a page can hold fewer than limit leads while has_more is
true; keep paging with next_cursor. place_in_line is only given on
a single-lead read.
status is one of: new, queued, scheduled, held, dialing, on_call, callback_scheduled, done, out_of_queue.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
external_id |
query | string | At most 200 characters. | |
campaign_id |
query | string (uuid) | ||
status |
query | string | One of new / queued / scheduled / held / dialing / on_call / callback_scheduled / done / out_of_queue. |
|
created[gte] |
query | string | Created at or after (ISO 8601 or unix seconds). | |
limit |
query | integer | Default 20. 1 to 100. |
|
starting_after |
query | string (uuid) |
curl -X GET 'https://v2-api.callview.ai/api/v1/external/leads' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET"const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/leads', { method: 'GET', headers: { Authorization: `Basic ${auth}` },});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os
import requests
res = requests.request( "GET", "https://v2-api.callview.ai/api/v1/external/leads", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/leads');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 With external_id, the one lead. Without it, a page of leads.
{ "data": { "id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "external_id": "crm-10442", "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "name": "LEAD_NAME", "phone": "+15555550142", "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 }}Errors
| Status | Codes |
|---|---|
| 400 | invalid_request |
| 401 | authentication_failed |
| 403 | permission_denied |
| 404 | not_found |
| 409 | ambiguous_external_id |
| 429 | rate_limited, too_many_concurrent_requests |
| 500 | internal_error |
Update a lead
Section titled “Update a lead”PATCH /leads/{id}
name, email, address (null or “” clears one), fields (merged: a key sent as “” is removed; other keys are kept), consent (replaced), external_id (only while the lead has none), call_now: false (drops the speed-to-lead priority). The phone can’t be changed (phone_cannot_change): add a new lead instead. Answers the updated lead. Idempotency-Key is honoured.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
id |
path | string (uuid) | yes | |
Idempotency-Key |
header | string | Any string you choose (1 to 255 visible characters), new for each new action. Send the same key again within 24 hours and you get the first answer back instead of a second lead or call. At most 255 characters. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string or null | At most 200 characters. | |
email |
string or null | At most 254 characters. | |
address |
string or null | At most 500 characters. | |
fields |
object | Merged: a key sent as “” is removed, other keys are kept. | |
consent |
Consent | ||
external_id |
string | Only while the lead has none. At most 200 characters. | |
call_now |
boolean | false drops the speed-to-lead priority. One of false. |
|
phone |
string | Can’t be changed (phone_cannot_change). Add a new lead instead. |
curl -X PATCH 'https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET" \ -H 'Idempotency-Key: patchLeadsId-0001' \ -H 'Content-Type: application/json' \ -d '{ "name": "Dana Smith-Lee", "fields": { "roof_age": "13", "old_field": "" }}'import { randomUUID } from 'node:crypto';
const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10', { method: 'PATCH', headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() }, body: JSON.stringify({ "name": "Dana Smith-Lee", "fields": { "roof_age": "13", "old_field": "" } }),});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os, uuid
import requests
res = requests.request( "PATCH", "https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "name": "Dana Smith-Lee", "fields": { "roof_age": "13", "old_field": "", }, }, timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'PATCH', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16))], CURLOPT_POSTFIELDS => <<<'JSON'{ "name": "Dana Smith-Lee", "fields": { "roof_age": "13", "old_field": "" }}JSON, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 The updated lead
{ "data": { "id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "external_id": "crm-10442", "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "name": "LEAD_NAME", "phone": "+15555550142", "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 }}Errors
| Status | Codes |
|---|---|
| 400 | invalid_request |
| 401 | authentication_failed |
| 403 | permission_denied |
| 404 | not_found |
| 409 | duplicate_lead, idempotency_mismatch, idempotency_in_progress |
| 422 | phone_cannot_change |
| 429 | rate_limited, too_many_concurrent_requests |
| 500 | internal_error |
| 503 | service_unavailable |
Opt a lead out - stop calling, add to Do Not Call
Section titled “Opt a lead out - stop calling, add to Do Not Call”POST /leads/{id}/opt_out
The number goes on the organization’s Do Not Call list (source “API”), so no campaign of the organization dials it again; the lead leaves the queue with the DNC outcome and its open callbacks are closed. A call in progress is NOT cut: the reply says call_in_progress true, and nothing dials the number again. Idempotent: a second opt_out answers the same, with no second Do Not Call entry. Answers the updated lead plus call_in_progress.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
id |
path | string (uuid) | yes | |
Idempotency-Key |
header | string | Any string you choose (1 to 255 visible characters), new for each new action. Send the same key again within 24 hours and you get the first answer back instead of a second lead or call. At most 255 characters. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
reason |
string | Why (kept in the audit log and on closed callbacks) At most 500 characters. |
curl -X POST 'https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10/opt_out' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET" \ -H 'Idempotency-Key: postLeadsIdOptOut-0001' \ -H 'Content-Type: application/json' \ -d '{ "reason": "Asked to stop on our web chat"}'import { randomUUID } from 'node:crypto';
const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10/opt_out', { method: 'POST', headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() }, body: JSON.stringify({ "reason": "Asked to stop on our web chat" }),});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os, uuid
import requests
res = requests.request( "POST", "https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10/opt_out", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "reason": "Asked to stop on our web chat", }, timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/leads/2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10/opt_out');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16))], CURLOPT_POSTFIELDS => <<<'JSON'{ "reason": "Asked to stop on our web chat"}JSON, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 The lead, plus call_in_progress
{ "data": { "id": "2b8e4c1d-9f3a-4b7e-8c21-6a5d4e3f2b10", "external_id": "crm-10442", "campaign_id": "7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01", "name": "LEAD_NAME", "phone": "+15555550142", "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, "call_in_progress": true }}Errors
| Status | Codes |
|---|---|
| 400 | invalid_request |
| 401 | authentication_failed |
| 403 | permission_denied |
| 404 | not_found |
| 409 | idempotency_mismatch, idempotency_in_progress |
| 429 | rate_limited, too_many_concurrent_requests |
| 500 | internal_error |
| 503 | service_unavailable |
List a campaign’s leads (older list)
Section titled “List a campaign’s leads (older list)”GET /campaigns/{campaignId}/leads
A campaign’s leads, page by page (cursor = the next_cursor you were given). A live key gets the older lead shape here; GET /leads with campaign_id gives the lead object and is the one to use for new work. Needs leads:read.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
campaignId |
path | string (uuid) | yes | |
cursor |
query | string | ||
limit |
query | integer | Default 50. 1 to 100. |
|
search |
query | string | Part of a name, phone or email. At most 200 characters. |
curl -X GET 'https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET"const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads', { method: 'GET', headers: { Authorization: `Basic ${auth}` },});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os
import requests
res = requests.request( "GET", "https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 A page of leads
{ "data": [ { "id": "e4d3c2b1-a09f-4e8d-9c7b-6a5f4e3d2c1b", "livemode": true } ], "meta": { "next_cursor": null, "has_more": false }}Errors
| Status | Codes |
|---|---|
| 400 | invalid_request |
| 401 | authentication_failed |
| 403 | permission_denied |
| 404 | not_found |
| 429 | rate_limited, too_many_concurrent_requests |
| 500 | internal_error |
Add a lead (older address)
Section titled “Add a lead (older address)”POST /campaigns/{campaignId}/leads
The same checks, queue and reply as POST /leads, with the campaign in the path. customFields is still accepted (it is merged into fields). Use POST /leads for new work.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
campaignId |
path | string (uuid) | yes | |
Idempotency-Key |
header | string | Any string you choose (1 to 255 visible characters), new for each new action. Send the same key again within 24 hours and you get the first answer back instead of a second lead or call. At most 255 characters. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
phone |
string | yes | At most 32 characters. |
external_id |
string | At most 200 characters. | |
name |
string | At most 200 characters. | |
email |
string | At most 254 characters. | |
address |
string | At most 500 characters. | |
fields |
object | ||
customFields |
object | ||
consent |
Consent | ||
call_now |
boolean | ||
dry_run |
boolean |
curl -X POST 'https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET" \ -H 'Idempotency-Key: crm-10442-postCampaignsCampaignIdLeads' \ -H 'Content-Type: application/json' \ -d '{ "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "call_now": true, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" }}'import { randomUUID } from 'node:crypto';
const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads', { method: 'POST', headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() }, body: JSON.stringify({ "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "call_now": true, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } }),});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os, uuid
import requests
res = requests.request( "POST", "https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "call_now": True, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote", }, }, timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16))], CURLOPT_POSTFIELDS => <<<'JSON'{ "external_id": "crm-10442", "phone": "+15550100001", "name": "Dana Smith", "call_now": true, "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" }}JSON, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 A dry run, or a re-queued lead
{ "data": { "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 }}201 Accepted (see POST /leads)
{ "data": { "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 }}Errors
Add up to 60 leads (older address)
Section titled “Add up to 60 leads (older address)”POST /campaigns/{campaignId}/leads/batch
The same as POST /leads/batch, with the campaign in the path.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
campaignId |
path | string (uuid) | yes | |
Idempotency-Key |
header | string | Any string you choose (1 to 255 visible characters), new for each new action. Send the same key again within 24 hours and you get the first answer back instead of a second lead or call. At most 255 characters. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
dry_run |
boolean | ||
leads |
array of object | yes | At most 60 items. |
leads[].phone |
string | yes | |
leads[].external_id |
string | ||
leads[].name |
string | ||
leads[].email |
string | ||
leads[].address |
string | ||
leads[].fields |
object | ||
leads[].customFields |
object | ||
leads[].consent |
Consent | ||
leads[].call_now |
boolean |
curl -X POST 'https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads/batch' \ -u "$CALLVIEW_KEY_ID:$CALLVIEW_SECRET" \ -H 'Idempotency-Key: postCampaignsCampaignIdLeadsBatch-0001' \ -H 'Content-Type: application/json' \ -d '{ "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } } ]}'import { randomUUID } from 'node:crypto';
const auth = Buffer.from(`${process.env.CALLVIEW_KEY_ID}:${process.env.CALLVIEW_SECRET}`).toString('base64');
const res = await fetch('https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads/batch', { method: 'POST', headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() }, body: JSON.stringify({ "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } } ] }),});console.log(res.status, res.headers.get('request-id'));console.log(await res.text());import os, uuid
import requests
res = requests.request( "POST", "https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads/batch", auth=(os.environ["CALLVIEW_KEY_ID"], os.environ["CALLVIEW_SECRET"]), headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote", }, }, ], }, timeout=30,)print(res.status_code, res.headers.get("Request-Id"))print(res.text)<?php$ch = curl_init('https://v2-api.callview.ai/api/v1/external/campaigns/7c1f2b9e-4a3d-4e8f-9b21-5d6a7e8f9a01/leads/batch');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_USERPWD => getenv('CALLVIEW_KEY_ID') . ':' . getenv('CALLVIEW_SECRET'), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16))], CURLOPT_POSTFIELDS => <<<'JSON'{ "leads": [ { "external_id": "crm-10443", "phone": "+15550100002", "consent": { "source": "web_form", "agreed_at": "2026-10-06T16:58:02Z", "url": "https://example.com/quote" } } ]}JSON, CURLOPT_HEADER => false,]);$body = curl_exec($ch);echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body, "\n";200 A result per lead (see POST /leads/batch)
{ "data": { "succeeded": 1, "failed": 1, "dry_run": true, "results": [ { "index": 1, "ok": true, "lead": { "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 } } ], "livemode": true }}Errors
| Status | Codes |
|---|---|
| 400 | invalid_request |
| 401 | authentication_failed |
| 403 | permission_denied |
| 404 | not_found |
| 409 | idempotency_mismatch, idempotency_in_progress |
| 429 | lead_rate_limited, rate_limited, too_many_concurrent_requests |
| 500 | internal_error |
| 503 | service_unavailable |