Skip to main content
Optional. /calls returns call records — the telephony activity behind your leads and contacts. If you do not implement it, return 404 — HunterAI skips it and the sync still succeeds. It is incremental.

Purpose

A call is one telephony record in your CRM — an inbound or outbound call a manager made or received. HunterAI reads /calls to compute call volume per manager and connect rate — the share of calls with duration_seconds > 0. Each call is linked to the lead or contact it concerns, so those metrics attribute per manager and per record.

Request

You must return calls 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 calls 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

Association

Every call must reference a lead or a contact. The link is resolved in this exact order:
  1. If lead_id is present, the call is linked to that lead. lead_id wins even when contact_id is also present — contact_id is then ignored.
  2. If lead_id is absent and contact_id is present, the call is linked to that contact.
  3. If neither is present, the call is rejected and fails its page — see Failure modes.
A call with neither lead_id nor contact_id fails the page, and because a failed page fails the calls entity, the whole call feed for that run is lost.This differs from /tasks: a task with neither is accepted (just linked to nothing), whereas a call with neither is rejected. Two similar endpoints, opposite behaviour — do not assume one from the other. Always send a lead_id (preferred) or a contact_id on every call.

Timestamp format

created_at and updated_at accept a calendar date and time in these concrete forms:
  • With an explicit offset — 2025-07-02T15:10:00+05:00.
  • With a trailing Z for UTC — 2025-07-02T15:10:00Z.
  • With no zone at all — 2025-07-02T15:10:00, which is read as UTC.
  • The date and time may be separated by a T or by a single space (2025-07-02 15:10: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 call is still ingested.
Consequence when this hits updated_at: a null updated_at carries no modification time, so the call cannot advance the watermark and is not selected by any later updated_since filter. In effect the call — 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 on analytics — do not spend effort populating them:
  • call_status, result, recording_link, source (provider), created_by — telephony extras that are not part of this spec; dropped from the mapped columns. Calls keep your full raw payload, so these survive in the raw copy for future reprocessing, but they are not surfaced in v1.0 analytics. See Limitations → Fields dropped entirely.
  • Any other key not listed in the Fields table — kept in the raw copy only, with no effect on analytics.

Identity & cross-references

  • Identity: id is the stable primary key. HunterAI upserts by (connection, id), so a call’s id must never change — a changed id is ingested as a brand-new call.
  • lead_id → resolves against /leads.
  • contact_id → resolves against /contacts.
  • responsible_user_id → resolves against /users.
  • Association between the call and a lead or contact follows the Association rule above.

Incremental sync

/calls is incremental. HunterAI sends updated_since; return only calls whose updated_at is greater than or equal to it (inclusive — re-returning the boundary call 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 calls ordered by updated_at ascending, so the watermark can advance page by page without skipping an older call on a later page.
  • updated_at must change on every modification of a call. A change that does not advance updated_at falls at or below the watermark and is never pulled again. Calls are usually immutable once logged, so in practice updated_at equals the log time.
See How sync works for the shared model.

Deletion behaviour

Calls have no tombstone — there is no is_deleted on a call. Because the upsert only inserts and updates, a call you delete in your CRM, or simply stop returning, is not removed from HunterAI; its historical row persists. Only leads support deletion, via is_deleted — see Limitations → Tombstones.

Failure modes

A page of calls (up to 100 records) is validated and written as one transaction. The consequences:
  • A single malformed call fails the whole page. If any call on a page cannot be processed — a missing id, an over-length id, a call with neither lead_id nor contact_id, or a non-numeric duration_seconds — none of the calls on that page are saved. This is page-level, not per-record.
  • A failed page fails the calls entity, not the whole sync. Because /calls is optional, a page that fails takes the calls entity down and reports it failed, but the overall sync still succeeds — unlike a required entity, whose failure fails the sync. Pages that already committed stay committed, and the next run resumes from the watermark.
  • A call with neither lead_id nor contact_id is rejected and fails its page. This is the opposite of /tasks, where an unassociated task is accepted and merely orphaned.
  • A non-numeric duration_seconds fails the page. duration_seconds must be an integer; a non-numeric value is rejected outright. This is stricter than price on /leads, which tolerates a numeric string — a known inconsistency between the two numeric fields. Send duration_seconds as a plain integer.
  • direction never fails. An unrecognised, null, or omitted direction is silently stored as out, so an inbound call mislabelled this way is counted as outbound without any error.
  • An unparseable timestamp does not fail anything — the field becomes null and the call is still ingested. See the consequence for updated_at above.
  • 429, 5xx, and request timeouts are retried with exponential backoff up to 5 times, then the entity fails; a timeout is retried the same way as a 5xx. HunterAI applies its request rate limit per connection, so two connections to the same base URL are throttled independently.
  • 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

/calls is optional. Returning 404 skips it cleanly — the entity reports success with zero records and the overall sync still succeeds.
Without /calls, HunterAI has no call volume per manager and no connect rate. Leads, contacts, tasks, and attribution are unaffected.