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
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:- If
lead_idis present, the call is linked to that lead.lead_idwins even whencontact_idis also present —contact_idis then ignored. - If
lead_idis absent andcontact_idis present, the call is linked to that contact. - If neither is present, the call is rejected and fails its page — see Failure modes.
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
Zfor 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
Tor by a single space (2025-07-02 15:10:00). - A bare date —
2025-07-02— is accepted; the time becomes00:00UTC.
null for that
field, and no error is raised — the call is still ingested.
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:
idis the stable primary key. HunterAI upserts by(connection, id), so a call’sidmust never change — a changedidis 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_atdrives the per-entity watermark — the largestupdated_atHunterAI has committed. The next run resumes from there.- You must return calls ordered by
updated_atascending, so the watermark can advance page by page without skipping an older call on a later page. updated_atmust change on every modification of a call. A change that does not advanceupdated_atfalls at or below the watermark and is never pulled again. Calls are usually immutable once logged, so in practiceupdated_atequals the log time.
Deletion behaviour
Calls have no tombstone — there is nois_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-lengthid, a call with neitherlead_idnorcontact_id, or a non-numericduration_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
/callsis 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_idnorcontact_idis rejected and fails its page. This is the opposite of/tasks, where an unassociated task is accepted and merely orphaned. - A non-numeric
duration_secondsfails the page.duration_secondsmust be an integer; a non-numeric value is rejected outright. This is stricter thanpriceon/leads, which tolerates a numeric string — a known inconsistency between the two numeric fields. Sendduration_secondsas a plain integer. directionnever fails. An unrecognised,null, or omitteddirectionis silently stored asout, so an inbound call mislabelled this way is counted as outbound without any error.- An unparseable timestamp does not fail anything — the field becomes
nulland the call is still ingested. See the consequence forupdated_atabove. 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 a5xx. 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./calls, HunterAI has no call volume per manager and no connect rate.
Leads, contacts, tasks, and attribution are unaffected.