HCC Docs

Leads

List leads, newest first

GET /v1/leads leads:read

Query

  • phone string

    The leads with this number

  • limit integer
  • cursor string

Responses

  • 200

    A page of leads

    Fields
    • data Lead[] required
      Fields
      • id string required
      • phone string required
      • name string | null required
      • timezone string | null required
      • source "api" | "csv" | "crm" | "dashboard" required
      • ref string | null required

        Your id for it, if you gave one

      • crmRef string | null required
      • recordUrl string | null required
      • fields object required
      • createdAt string required
      • updatedAt string required
    • nextCursor string | null required
  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.code

Add a lead, or update the one with the same `ref`

POST /v1/leads leads:write

Headers

  • Idempotency-Key string

    Makes 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 422 idempotency_key_reused, and while the first request is still running a 409 idempotency_key_in_use. Server errors, 409s and 429s are not stored.

Body

  • phone string required
  • name string | null
  • timezone string | null
  • ref string | null
  • crmRef string | null
  • recordUrl string | null
  • fields object
  • consent object
    Fields
    • type "express" | "express_written" | "other" required
    • scopes "human_calls" | "ai_voice" | "prerecorded"[] required
    • source string required
    • proofUrl string | null
    • proofText string | null
    • effectiveAt string required
  • campaign string

    Goes through this campaign alone, instead of every running one with an onLead or 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 same ref, or with the same number if you give no ref. refuse writes nothing and answers 409 lead_exists, with the leads in error.details.leads, if a lead has this ref, or, with no ref, if any lead has this number, a CRM lead included. A new ref on a number that has leads adds a second lead, as people share lines. The leads are ids alone without the leads:read scope.

Responses

  • 200

    Already known: updated if anything changed

    Fields
    • data Lead required
      Fields
      • id string required
      • phone string required
      • name string | null required
      • timezone string | null required
      • source "api" | "csv" | "crm" | "dashboard" required
      • ref string | null required

        Your id for it, if you gave one

      • crmRef string | null required
      • recordUrl string | null required
      • fields object required
      • createdAt string required
      • updatedAt string required
  • 201

    Added

    Fields
    • data Lead required
      Fields
      • id string required
      • phone string required
      • name string | null required
      • timezone string | null required
      • source "api" | "csv" | "crm" | "dashboard" required
      • ref string | null required

        Your id for it, if you gave one

      • crmRef string | null required
      • recordUrl string | null required
      • fields object required
      • createdAt string required
      • updatedAt string required
  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 409

    lead_exists, with ifExists: "refuse": nothing was written. Or idempotency_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 by error.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-Key string

    Makes 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 422 idempotency_key_reused, and while the first request is still running a 409 idempotency_key_in_use. Server errors, 409s and 429s are not stored.

Body

  • leads object[] required
    Fields
    • phone string required
    • name string | null
    • timezone string | null
    • ref string | null
    • crmRef string | null
    • recordUrl string | null
    • fields object
    • consent object
      Fields
      • type "express" | "express_written" | "other" required
      • scopes "human_calls" | "ai_voice" | "prerecorded"[] required
      • source string required
      • proofUrl string | null
      • proofText string | null
      • effectiveAt string required
  • campaign string

    Goes through this campaign alone, instead of every running one with an onLead or an .enroll. A lead that doesn't match the campaign's filter is skipped.

Responses

  • 200

    What changed

    Fields
    • data Lead[] required

      The leads created or changed

      Fields
      • id string required
      • phone string required
      • name string | null required
      • timezone string | null required
      • source "api" | "csv" | "crm" | "dashboard" required
      • ref string | null required

        Your id for it, if you gave one

      • crmRef string | null required
      • recordUrl string | null required
      • fields object required
      • createdAt string required
      • updatedAt string required
    • created integer required
    • updated integer required
    • unchanged integer required
    • skipped integer required

      Leads 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 by error.code

List CSV imports, newest first

GET /v1/leads/imports leads:read

Query

  • limit integer
  • cursor string

Responses

  • 200

    A page of imports

    Fields
    • data LeadImport[] required
      Fields
      • id string required
      • fileName string | null required
      • status "uploaded" | "running" | "completed" | "failed" required

        One whose job dies ends as failed, within about 20 minutes. The rows it reached stay.

      • rows integer required
      • created integer required
      • updated integer required
      • invalid integer required
      • skipped integer required

        Rows whose lead doesn't match the filter of the campaign this file is for

      • doNotCall integer required

        Rows whose number is on the Do Not Call list: stored, and no campaign takes them

      • dncAdded integer required

        Numbers this file added to the Do Not Call list, through its dnc column

      • defaultTimezone string | null required
      • errors object[] required

        The first rows that could not be read. The rest are in the error file.

      • hasErrorFile boolean required

        Whether GET /leads/imports/{id}/errors has a file

      • failure string | null required
      • createdBy string | null required

        The API key or user that uploaded it

      • createdAt string required
      • updatedAt string required

        Its last sign of life

      • finishedAt string | null required
    • nextCursor string | null required
  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.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

  • phone string

    The phone column. Default phone

  • name string

    The name column. Default name. Empty: not read, whatever the file has

  • timezone string

    The time zone column. Default timezone. Empty: not read, whatever the file has

  • ref string

    The column with your id. Default ref. Empty: not read, whatever the file has

  • recordUrl string

    The link column. Default record_url. Empty: not read, whatever the file has

  • fields string

    Columns to keep in fields, as one CSV row: put a name with a comma in double quotes. Nothing else in the file is stored.

  • dnc string

    A column of numbers to add to the Do Not Call list: yes, true, 1 or x adds it, and a blank, no, false or 0 does nothing. It never takes a number off. Anything else refuses the row. Needs the dnc:write scope. Not read unless you name it.

  • consent string

    A column saying what each number agreed to. yes, true, 1, x or granted is a grant, which needs its date in consentDate. revoked, stop or opt out is a revocation. A blank or a no records nothing. What cannot be read in these columns never refuses the row: the lead is imported and the line reported in errors, 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 the consent:write scope. Not read unless you name it.

  • consentDate string

    The column with the date consent was given or revoked.

  • consentType string

    The column with the type: express, express_written or other.

  • consentScopes string

    The column with what it covers: any of human_calls, ai_voice and prerecorded. On a grant, a blank means human_calls only. On a revocation, every scope, which also adds the number to the Do Not Call list.

  • consentSource string

    The column saying where it came from: the form, the page or the rep.

  • consentProofUrl string

    The column with a link to the proof.

  • consentProofText string

    The column with the wording the person agreed to.

  • defaultTimezone string

    The 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.

  • fileName string

    What to call this file in the import history

Headers

  • Idempotency-Key string

    Makes 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 422 idempotency_key_reused, and while the first request is still running a 409 idempotency_key_in_use. Server errors, 409s and 429s are not stored.

Responses

  • 202

    Accepted

    Fields
    • data LeadImport required
      Fields
      • id string required
      • fileName string | null required
      • status "uploaded" | "running" | "completed" | "failed" required

        One whose job dies ends as failed, within about 20 minutes. The rows it reached stay.

      • rows integer required
      • created integer required
      • updated integer required
      • invalid integer required
      • skipped integer required

        Rows whose lead doesn't match the filter of the campaign this file is for

      • doNotCall integer required

        Rows whose number is on the Do Not Call list: stored, and no campaign takes them

      • dncAdded integer required

        Numbers this file added to the Do Not Call list, through its dnc column

      • defaultTimezone string | null required
      • errors object[] required

        The first rows that could not be read. The rest are in the error file.

      • hasErrorFile boolean required

        Whether GET /leads/imports/{id}/errors has a file

      • failure string | null required
      • createdBy string | null required

        The API key or user that uploaded it

      • createdAt string required
      • updatedAt string required

        Its last sign of life

      • finishedAt string | 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 by error.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

  • rows object[] required
    Fields
    • phone string required
    • ref string | null
    • timezone string | null

Responses

  • 200

    What it would do

    Fields
    • data LeadImportCheck required
      Fields
      • created integer required

        Leads these rows would add

      • updated integer required

        Existing leads these rows would write to

      • ambiguous integer required

        Rows that would be refused, as their number is on several leads

      • doNotCall integer | null required

        Rows whose number is on the Do Not Call list. Null without the dnc:read scope.

      • noTimezone integer required

        Leads 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 by error.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
    • data LeadImportCampaign[] required
      Fields
      • name string required
      • onLead boolean required
      • filtered boolean required

        Whether it has an .enroll

      • filterFields string[] required

        The fields its filter names

      • reenroll boolean required

        Whether 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 by error.code

How a CSV import went

GET /v1/leads/imports/{id} leads:read

Path

  • id string required

Responses

  • 200

    The import

    Fields
    • data LeadImport required
      Fields
      • id string required
      • fileName string | null required
      • status "uploaded" | "running" | "completed" | "failed" required

        One whose job dies ends as failed, within about 20 minutes. The rows it reached stay.

      • rows integer required
      • created integer required
      • updated integer required
      • invalid integer required
      • skipped integer required

        Rows whose lead doesn't match the filter of the campaign this file is for

      • doNotCall integer required

        Rows whose number is on the Do Not Call list: stored, and no campaign takes them

      • dncAdded integer required

        Numbers this file added to the Do Not Call list, through its dnc column

      • defaultTimezone string | null required
      • errors object[] required

        The first rows that could not be read. The rest are in the error file.

      • hasErrorFile boolean required

        Whether GET /leads/imports/{id}/errors has a file

      • failure string | null required
      • createdBy string | null required

        The API key or user that uploaded it

      • createdAt string required
      • updatedAt string required

        Its last sign of life

      • finishedAt string | null required
  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.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

  • id string required

Responses

  • 200

    A CSV of line and reason

  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.code

Get a lead

GET /v1/leads/{id} leads:read

Path

  • id string required

Responses

  • 200

    The lead

    Fields
    • data Lead required
      Fields
      • id string required
      • phone string required
      • name string | null required
      • timezone string | null required
      • source "api" | "csv" | "crm" | "dashboard" required
      • ref string | null required

        Your id for it, if you gave one

      • crmRef string | null required
      • recordUrl string | null required
      • fields object required
      • createdAt string required
      • updatedAt string required
  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.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

  • id string required

Responses

  • 204

    Deleted

  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.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

  • id string required

Query

  • limit integer
  • cursor string

Responses

  • 200

    Its rows, newest first

    Fields
    • data LeadConsent[] required
      Fields
      • id string required
      • phone string required
      • leadId string | null required

        The 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
      • source string required
      • proofUrl string | null required
      • proofText string | null required
      • effectiveAt string required
      • ingest "api" | "csv" | "crm" | "dnc" | "dashboard" required
      • actor string | null required

        The API key, user or connection

      • createdAt string required
    • nextCursor string | null required
    • current object required

      Where this number stands, per scope

      Fields
      • human_calls object required
      • ai_voice object required
      • prerecorded object required
  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.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

  • id string required

Headers

  • Idempotency-Key string

    Makes 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 422 idempotency_key_reused, and while the first request is still running a 409 idempotency_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
  • source string required
  • proofUrl string | null
  • proofText string | null
  • effectiveAt string required

Responses

  • 201

    Recorded

    Fields
    • data object required
      Fields
      • current object required

        Where this number stands, per scope

      • recorded LeadConsent[] required

        The 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 by error.code

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

  • id string required

Headers

  • Idempotency-Key string

    Makes 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 422 idempotency_key_reused, and while the first request is still running a 409 idempotency_key_in_use. Server errors, 409s and 429s are not stored.

Body

  • scopes "human_calls" | "ai_voice" | "prerecorded"[]
  • source string
  • proofUrl string | null
  • proofText string | null
  • effectiveAt string

Responses

  • 201

    Recorded

    Fields
    • data object required
      Fields
      • current object required

        Where this number stands, per scope

      • recorded LeadConsent[] required

        The 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 by error.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

  • id string required

Body

  • campaign string

Responses

  • 202

    On its way

  • 400

    Invalid request

  • 401

    Missing or invalid API key

  • 404

    Not found

  • 429

    rate_limited, or another limit named by error.code