Leads
List leads, newest first
GET /v1/leads leads:read
Query
phonestringThe leads with this number
limitintegercursorstring
Responses
- 200
A page of leads
Fields
-
dataLead[] requiredFields
-
idstring required -
phonestring required -
namestring | null required -
timezonestring | null required -
source"api" | "csv" | "crm" | "dashboard" required -
refstring | null requiredYour id for it, if you gave one
-
crmRefstring | null required -
recordUrlstring | null required -
fieldsobject required -
createdAtstring required -
updatedAtstring required
-
-
nextCursorstring | null required
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
Add a lead, or update the one with the same `ref`
POST /v1/leads leads:write
Headers
Idempotency-KeystringMakes the request safe to retry. For 24h, a request with the same key and body replays the stored response (with
Idempotent-Replayed: true); the same key with a different request is a 422idempotency_key_reused, and while the first request is still running a 409idempotency_key_in_use. Server errors, 409s and 429s are not stored.
Body
-
phonestring required -
namestring | null -
timezonestring | null -
refstring | null -
crmRefstring | null -
recordUrlstring | null -
fieldsobject -
consentobjectFields
-
type"express" | "express_written" | "other" required -
scopes"human_calls" | "ai_voice" | "prerecorded"[] required -
sourcestring required -
proofUrlstring | null -
proofTextstring | null -
effectiveAtstring required
-
-
campaignstringGoes through this campaign alone, instead of every running one with an
onLeador an.enroll. A lead that doesn't match the campaign's filter is skipped. -
ifExists"update" | "refuse"update, the default, adds the lead or overwrites the one with the sameref, or with the same number if you give noref.refusewrites nothing and answers 409lead_exists, with the leads inerror.details.leads, if a lead has thisref, or, with noref, if any lead has this number, a CRM lead included. A newrefon a number that has leads adds a second lead, as people share lines. The leads are ids alone without theleads:readscope.
Responses
- 200
Already known: updated if anything changed
Fields
-
dataLead requiredFields
-
idstring required -
phonestring required -
namestring | null required -
timezonestring | null required -
source"api" | "csv" | "crm" | "dashboard" required -
refstring | null requiredYour id for it, if you gave one
-
crmRefstring | null required -
recordUrlstring | null required -
fieldsobject required -
createdAtstring required -
updatedAtstring required
-
-
- 201
Added
Fields
-
dataLead requiredFields
-
idstring required -
phonestring required -
namestring | null required -
timezonestring | null required -
source"api" | "csv" | "crm" | "dashboard" required -
refstring | null requiredYour id for it, if you gave one
-
crmRefstring | null required -
recordUrlstring | null required -
fieldsobject required -
createdAtstring required -
updatedAtstring required
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 409
lead_exists, withifExists: "refuse": nothing was written. Oridempotency_key_in_use: a request with this key is still in progress - 422
idempotency_key_reused: this key was used with a different request - 429
rate_limited, or another limit named byerror.code
Add or update up to 500 leads
POST /v1/leads/batch leads:write
One statement for the whole batch. For anything larger, upload a file to /leads/imports.
Headers
Idempotency-KeystringMakes the request safe to retry. For 24h, a request with the same key and body replays the stored response (with
Idempotent-Replayed: true); the same key with a different request is a 422idempotency_key_reused, and while the first request is still running a 409idempotency_key_in_use. Server errors, 409s and 429s are not stored.
Body
-
leadsobject[] requiredFields
-
phonestring required -
namestring | null -
timezonestring | null -
refstring | null -
crmRefstring | null -
recordUrlstring | null -
fieldsobject -
consentobjectFields
-
type"express" | "express_written" | "other" required -
scopes"human_calls" | "ai_voice" | "prerecorded"[] required -
sourcestring required -
proofUrlstring | null -
proofTextstring | null -
effectiveAtstring required
-
-
-
campaignstringGoes through this campaign alone, instead of every running one with an
onLeador an.enroll. A lead that doesn't match the campaign's filter is skipped.
Responses
- 200
What changed
Fields
-
dataLead[] requiredThe leads created or changed
Fields
-
idstring required -
phonestring required -
namestring | null required -
timezonestring | null required -
source"api" | "csv" | "crm" | "dashboard" required -
refstring | null requiredYour id for it, if you gave one
-
crmRefstring | null required -
recordUrlstring | null required -
fieldsobject required -
createdAtstring required -
updatedAtstring required
-
-
createdinteger required -
updatedinteger required -
unchangedinteger required -
skippedinteger requiredLeads that don't match the filter of the campaign you named
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 409
idempotency_key_in_use: a request with this key is still running - 422
idempotency_key_reused: this key was used with a different request - 429
rate_limited, or another limit named byerror.code
List CSV imports, newest first
GET /v1/leads/imports leads:read
Query
limitintegercursorstring
Responses
- 200
A page of imports
Fields
-
dataLeadImport[] requiredFields
-
idstring required -
fileNamestring | null required -
status"uploaded" | "running" | "completed" | "failed" requiredOne whose job dies ends as
failed, within about 20 minutes. The rows it reached stay. -
rowsinteger required -
createdinteger required -
updatedinteger required -
invalidinteger required -
skippedinteger requiredRows whose lead doesn't match the filter of the campaign this file is for
-
doNotCallinteger requiredRows whose number is on the Do Not Call list: stored, and no campaign takes them
-
dncAddedinteger requiredNumbers this file added to the Do Not Call list, through its
dnccolumn -
defaultTimezonestring | null required -
errorsobject[] requiredThe first rows that could not be read. The rest are in the error file.
-
hasErrorFileboolean requiredWhether
GET /leads/imports/{id}/errorshas a file -
failurestring | null required -
createdBystring | null requiredThe API key or user that uploaded it
-
createdAtstring required -
updatedAtstring requiredIts last sign of life
-
finishedAtstring | null required
-
-
nextCursorstring | null required
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
Import leads from a CSV file
POST /v1/leads/imports leads:write
The body is the file (text/csv, at most 64 MB). It goes to storage and a job upserts it in batches, so this answers at once; follow it at GET /leads/imports/{id}. The file is deleted once read, and only the mapped columns are kept.
A row with a ref is the lead with that ref. If the ref is new, it goes on a lead with that number and no ref, and otherwise makes a new lead: people can share a line. A row with no ref is the lead with its number, which is read as a US number, and is refused if that number is on several leads, as nothing says which it means. A missing column or a blank cell keeps what the lead holds, and fields adds to what it has. New leads enter running campaigns with an onLead or an .enroll, and updated ones are told as changed, so a new value in a field a filter names can bring back a lead that left (reenroll: false stops that). Numbers on the Do Not Call list are stored and counted, and no campaign takes them.
Query
phonestringThe phone column. Default
phonenamestringThe name column. Default
name. Empty: not read, whatever the file hastimezonestringThe time zone column. Default
timezone. Empty: not read, whatever the file hasrefstringThe column with your id. Default
ref. Empty: not read, whatever the file hasrecordUrlstringThe link column. Default
record_url. Empty: not read, whatever the file hasfieldsstringColumns to keep in
fields, as one CSV row: put a name with a comma in double quotes. Nothing else in the file is stored.dncstringA column of numbers to add to the Do Not Call list:
yes,true,1orxadds it, and a blank,no,falseor0does nothing. It never takes a number off. Anything else refuses the row. Needs thednc:writescope. Not read unless you name it.consentstringA column saying what each number agreed to.
yes,true,1,xorgrantedis a grant, which needs its date inconsentDate.revoked,stoporopt outis a revocation. A blank or anorecords nothing. What cannot be read in these columns never refuses the row: the lead is imported and the line reported inerrors, with no grant recorded, while a revocation still counts, dated at import if its date cannot be read. This keeps evidence: it is not legal clearance. Needs theconsent:writescope. Not read unless you name it.consentDatestringThe column with the date consent was given or revoked.
consentTypestringThe column with the type:
express,express_writtenorother.consentScopesstringThe column with what it covers: any of
human_calls,ai_voiceandprerecorded. On a grant, a blank meanshuman_callsonly. On a revocation, every scope, which also adds the number to the Do Not Call list.consentSourcestringThe column saying where it came from: the form, the page or the rep.
consentProofUrlstringThe column with a link to the proof.
consentProofTextstringThe column with the wording the person agreed to.
defaultTimezonestringThe zone you say blank rows are in, as an IANA name. It goes on new leads and on leads with no zone, never over a zone a lead holds. A lead with no zone is never dialed.
fileNamestringWhat to call this file in the import history
Headers
Idempotency-KeystringMakes the request safe to retry. For 24h, a request with the same key and body replays the stored response (with
Idempotent-Replayed: true); the same key with a different request is a 422idempotency_key_reused, and while the first request is still running a 409idempotency_key_in_use. Server errors, 409s and 429s are not stored.
Responses
- 202
Accepted
Fields
-
dataLeadImport requiredFields
-
idstring required -
fileNamestring | null required -
status"uploaded" | "running" | "completed" | "failed" requiredOne whose job dies ends as
failed, within about 20 minutes. The rows it reached stay. -
rowsinteger required -
createdinteger required -
updatedinteger required -
invalidinteger required -
skippedinteger requiredRows whose lead doesn't match the filter of the campaign this file is for
-
doNotCallinteger requiredRows whose number is on the Do Not Call list: stored, and no campaign takes them
-
dncAddedinteger requiredNumbers this file added to the Do Not Call list, through its
dnccolumn -
defaultTimezonestring | null required -
errorsobject[] requiredThe first rows that could not be read. The rest are in the error file.
-
hasErrorFileboolean requiredWhether
GET /leads/imports/{id}/errorshas a file -
failurestring | null required -
createdBystring | null requiredThe API key or user that uploaded it
-
createdAtstring required -
updatedAtstring requiredIts last sign of life
-
finishedAtstring | null required
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 409
idempotency_key_in_use: a request with this key is still running - 422
idempotency_key_reused: this key was used with a different request - 429
rate_limited, or another limit named byerror.code
See what importing some rows would do, writing nothing
POST /v1/leads/imports/check leads:read
Up to 1000 rows at a time, already read: send each lead once. For a preview, so its numbers can be a little stale. doNotCall needs the dnc:read scope, and is null without it.
Body
-
rowsobject[] requiredFields
-
phonestring required -
refstring | null -
timezonestring | null
-
Responses
- 200
What it would do
Fields
-
dataLeadImportCheck requiredFields
-
createdinteger requiredLeads these rows would add
-
updatedinteger requiredExisting leads these rows would write to
-
ambiguousinteger requiredRows that would be refused, as their number is on several leads
-
doNotCallinteger | null requiredRows whose number is on the Do Not Call list. Null without the
dnc:readscope. -
noTimezoneinteger requiredLeads that would still have no zone, so would never be dialed
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
The running campaigns new leads would enter
GET /v1/leads/imports/campaigns campaigns:read
Those with an onLead or an .enroll. One with a filter also hears of a lead that changes.
Responses
- 200
The campaigns
Fields
-
dataLeadImportCampaign[] requiredFields
-
namestring required -
onLeadboolean required -
filteredboolean requiredWhether it has an
.enroll -
filterFieldsstring[] requiredThe fields its filter names
-
reenrollboolean requiredWhether a new value in one of those brings back a lead that left
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
How a CSV import went
GET /v1/leads/imports/{id} leads:read
Path
idstring required
Responses
- 200
The import
Fields
-
dataLeadImport requiredFields
-
idstring required -
fileNamestring | null required -
status"uploaded" | "running" | "completed" | "failed" requiredOne whose job dies ends as
failed, within about 20 minutes. The rows it reached stay. -
rowsinteger required -
createdinteger required -
updatedinteger required -
invalidinteger required -
skippedinteger requiredRows whose lead doesn't match the filter of the campaign this file is for
-
doNotCallinteger requiredRows whose number is on the Do Not Call list: stored, and no campaign takes them
-
dncAddedinteger requiredNumbers this file added to the Do Not Call list, through its
dnccolumn -
defaultTimezonestring | null required -
errorsobject[] requiredThe first rows that could not be read. The rest are in the error file.
-
hasErrorFileboolean requiredWhether
GET /leads/imports/{id}/errorshas a file -
failurestring | null required -
createdBystring | null requiredThe API key or user that uploaded it
-
createdAtstring required -
updatedAtstring requiredIts last sign of life
-
finishedAtstring | null required
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
The rows of an import that could not be read, as a CSV file
GET /v1/leads/imports/{id}/errors leads:read
The line and the reason of each, and never a cell of your file. Capped: the import says how many there were in all. There is a file when hasErrorFile is true.
Path
idstring required
Responses
- 200
A CSV of
lineandreason - 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
Get a lead
GET /v1/leads/{id} leads:read
Path
idstring required
Responses
- 200
The lead
Fields
-
dataLead requiredFields
-
idstring required -
phonestring required -
namestring | null required -
timezonestring | null required -
source"api" | "csv" | "crm" | "dashboard" required -
refstring | null requiredYour id for it, if you gave one
-
crmRefstring | null required -
recordUrlstring | null required -
fieldsobject required -
createdAtstring required -
updatedAtstring required
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
Delete a lead
DELETE /v1/leads/{id} leads:write
Its row and fields go, and it leaves every campaign. Dial attempts keep the number and decision, for the audit.
Path
idstring required
Responses
- 204
Deleted
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
What this lead's number has agreed to, and everything said about it
GET /v1/leads/{id}/consents consent:read
This keeps evidence of what a number agreed to: it is not legal clearance. Consent is by phone number, and the rows are never changed: the latest by effectiveAt stands, per scope, so only a newer grant undoes a revocation.
Path
idstring required
Query
limitintegercursorstring
Responses
- 200
Its rows, newest first
Fields
-
dataLeadConsent[] requiredFields
-
idstring required -
phonestring required -
leadIdstring | null requiredThe lead it came in on. Consent is by number, and outlives the lead.
-
kind"granted" | "revoked" required -
type"express" | "express_written" | "other" required -
scope"human_calls" | "ai_voice" | "prerecorded" required -
sourcestring required -
proofUrlstring | null required -
proofTextstring | null required -
effectiveAtstring required -
ingest"api" | "csv" | "crm" | "dnc" | "dashboard" required -
actorstring | null requiredThe API key, user or connection
-
createdAtstring required
-
-
nextCursorstring | null required -
currentobject requiredWhere this number stands, per scope
Fields
-
human_callsobject required -
ai_voiceobject required -
prerecordedobject required
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code
Record that this number agreed to be called
POST /v1/leads/{id}/consents consent:write
This keeps evidence of what a number agreed to: it is not legal clearance. Consent is by phone number, and the rows are never changed: the latest by effectiveAt stands, per scope, so only a newer grant undoes a revocation. A grant needs the date it was given, and can't be in the future. It never takes a number off the Do Not Call list.
Path
idstring required
Headers
Idempotency-KeystringMakes the request safe to retry. For 24h, a request with the same key and body replays the stored response (with
Idempotent-Replayed: true); the same key with a different request is a 422idempotency_key_reused, and while the first request is still running a 409idempotency_key_in_use. Server errors, 409s and 429s are not stored.
Body
-
type"express" | "express_written" | "other" required -
scopes"human_calls" | "ai_voice" | "prerecorded"[] required -
sourcestring required -
proofUrlstring | null -
proofTextstring | null -
effectiveAtstring required
Responses
- 201
Recorded
Fields
-
dataobject requiredFields
-
currentobject requiredWhere this number stands, per scope
-
recordedLeadConsent[] requiredThe rows this added. Saying the same thing twice adds none.
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 409
idempotency_key_in_use: a request with this key is still running - 422
idempotency_key_reused: this key was used with a different request - 429
rate_limited, or another limit named byerror.code
Record that this number took its consent back
POST /v1/leads/{id}/consents/revoke consent:write
This keeps evidence of what a number agreed to: it is not legal clearance. Consent is by phone number, and the rows are never changed: the latest by effectiveAt stands, per scope, so only a newer grant undoes a revocation. With no scopes, it's every scope, which also adds the number to the Do Not Call list. The Dialer hears straight away.
Path
idstring required
Headers
Idempotency-KeystringMakes the request safe to retry. For 24h, a request with the same key and body replays the stored response (with
Idempotent-Replayed: true); the same key with a different request is a 422idempotency_key_reused, and while the first request is still running a 409idempotency_key_in_use. Server errors, 409s and 429s are not stored.
Body
-
scopes"human_calls" | "ai_voice" | "prerecorded"[] -
sourcestring -
proofUrlstring | null -
proofTextstring | null -
effectiveAtstring
Responses
- 201
Recorded
Fields
-
dataobject requiredFields
-
currentobject requiredWhere this number stands, per scope
-
recordedLeadConsent[] requiredThe rows this added. Saying the same thing twice adds none.
-
-
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 409
idempotency_key_in_use: a request with this key is still running - 422
idempotency_key_reused: this key was used with a different request - 429
rate_limited, or another limit named byerror.code
Complete a lead
POST /v1/leads/{id}/complete campaigns:write
Your code says this lead is finished: it leaves its campaigns, or the one you name, at any step. Exits and goals run on it, off a queue: an .exit that matches makes it exit, anything else goal. Answers before that has happened.
Path
idstring required
Body
-
campaignstring
Responses
- 202
On its way
- 400
Invalid request
- 401
Missing or invalid API key
- 404
Not found
- 429
rate_limited, or another limit named byerror.code