Skip to main content
Required. /leads returns your leads. It is incremental and syncs after /pipelines, so each lead’s won/lost can be resolved from its stage.

Purpose

A lead is one sales opportunity in your CRM. HunterAI reads /leads to compute revenue, win-rate, conversion, and goal progress (from the lead’s value, its stage, and its timestamps), traffic and source analytics (from source and the utm fields), loss analysis (from loss_reason), and per-manager and per-contact attribution. This is the highest-value endpoint: if it is empty, almost every metric is empty.

Request

You must return the leads ordered by updated_at ascending. HunterAI advances its watermark page by page and relies on that order; an out-of-order page can cause older leads on a later page to be skipped.

Response envelope

200 OK
The envelope (data, has_more, page, limit) is the same on every endpoint — see How sync works → The envelope. Only data and has_more are read.

Fields

loss_reason needs no separate endpoint. Send the reason as free text on the lead; HunterAI synthesizes a stable loss-reason record from the text and links the lead to it. Identical text across leads maps to the same record.

Timestamp format

created_at, updated_at, and closed_at accept a calendar date and time in these concrete forms:
  • With an explicit offset — 2025-07-02T16:45:00+05:00.
  • With a trailing Z for UTC — 2025-07-02T16:45:00Z.
  • With no zone at all — 2025-07-02T16:45:00, which is read as UTC.
  • The date and time may be separated by a T or by a single space (2025-07-02 16:45:00).
  • A bare date — 2025-07-02 — is accepted; the time becomes 00:00 UTC.
Prefer the explicit-offset form. An unparseable value becomes null for that field, and no error is raised — the lead is still ingested.
Consequence when this hits updated_at: a null updated_at carries no modification time, so the lead cannot advance the watermark and is not selected by any later updated_since filter. In effect the lead — and every future change to it — drops out of incremental sync. Keep updated_at a valid timestamp that advances on every change.

Fields the adapter ignores

Sending any of these has no effect — do not spend effort populating them:
  • currency — dropped. price is stored as a plain number with no currency attached, so a connection is effectively single-currency. See Limitations → Currency.
  • contact_ids beyond the first element — only contact_ids[0] is kept; the rest are ignored.
  • is_won / is_lost — there is no such field on a lead. Won/lost is derived from the lead’s stage kind, never from the lead itself.
  • Any other key not listed in the Fields table — ignored.

Identity & cross-references

  • Identity: id is the stable primary key. HunterAI upserts by (connection, id), so id must never change for the same lead — a changed id is ingested as a brand-new lead.
  • pipeline_id → resolves against /pipelines.
  • stage_id → resolves against the stages nested in /pipelines. The stage’s kind is what makes a lead won or lost — a kind outside open, won, or lost is an error at /pipelines, not something the lead can override.
  • responsible_user_id → resolves against /users.
  • contact_ids[0] → resolves against /contacts.

Incremental sync

/leads is incremental. HunterAI sends updated_since; return only leads whose updated_at is greater than or equal to it (inclusive — re-returning the boundary lead is harmless, since the upsert is idempotent).
  • updated_at drives the per-entity watermark — the largest updated_at HunterAI has committed. The next run resumes from there.
  • You must return leads ordered by updated_at ascending, so the watermark can advance page by page without skipping an older lead on a later page.
  • updated_at must change on every modification of a lead — a stage move, an owner change, a field edit, or a soft-delete. A change that does not advance updated_at falls at or below the watermark and is never pulled again.
See How sync works for the shared model.

Deletion behaviour

Leads are the only entity that supports a tombstone. Set is_deleted: true to soft-delete a lead; it defaults to false. A lead that simply stops appearing in your responses is not deleted — the upsert only inserts and updates, never removes a row. See Limitations → Tombstones.

Failure modes

A page of leads (up to 100 records) is validated and written as one transaction. The consequences of that:
  • A single malformed lead fails the whole page. If any lead on a page cannot be processed — a missing id, a non-numeric price — none of the ~100 leads on that page are saved. This is page-level, not per-record.
  • A failed page fails the sync. Because /leads is required, a page that fails takes the leads entity down with it, and the overall sync is reported failed. Pages that already committed stay committed, and the next run resumes from the watermark.
  • A lead whose stage was not synced cannot be classified. It lands with is_won = is_lost = false; this is counted and surfaced in the run summary, not silent. Always return every stage a lead can reference on /pipelines.
  • An unparseable timestamp does not fail anything — the field becomes null and the lead is still ingested. See the consequence for updated_at above.
  • 429 responses are retried with backoff up to 5 times, then the entity fails. HunterAI applies its request rate limit per connection, so two connections to the same base URL are throttled independently.
  • 5xx responses and request timeouts are retried with exponential backoff up to 5 times; a request that times out is retried the same way as a 5xx. Persistent failure fails the entity.
  • A repeated or unbounded page — a page whose IDs match the previous page, or a run past the page ceiling — fails the entity. See How sync works → Paging.

If this endpoint is missing

/leads is required. A 404 fails the leads entity and the sync is reported failed. This is the core entity, so revenue, win-rate, conversion, loss analysis, and per-manager and per-contact attribution would all be empty.