Skip to content

Core concepts

The public contract is intentionally smaller than Talki’s internal data model. Dashboard-only filing state, billing details, provider diagnostics, AI working memory, transcripts, and audio do not become public just because Talki stores or computes them.

REST responses and data.object in webhooks use the same serializers. When the same Call appears through both surfaces, its public fields have the same names and meanings.

  • Talki’s database is authoritative for appointments Talki owns. Whether a booking may be committed is decided there, under a lock, after a recount.
  • External calendars contribute busy time. Google and Outlook events that Talki does not own become constraints the agent honours during a call. They never decide whether something may be booked.

A connected CRM calendar can eventually follow the same rule: it may block time, but Talki decides whether one of its own appointments commits.

Treat every resource ID as an opaque, case-sensitive string. Do not parse it, infer a resource type from its shape, or generate one yourself. Store the Talki ID alongside your own record so webhook redelivery updates instead of duplicates.

All public timestamps are RFC 3339 UTC and end in Z. Filter values should also include an offset. GET /v1/account returns the account’s IANA timezone for UI and scheduling contexts.

  • started_at is when the call arrived and is the call-list ordering key.
  • answered_at is when Talki answered.
  • ended_at is an observed teardown time and can be null for older rows.
  • duration_seconds is the billed span. Do not recompute it from timestamps; small differences are expected.
  • created_at is when the completed call record was written. Use this field for incremental recovery.

A phone number in E.164 is how a caller is matched to a contact. Matching is narrower than you might expect on purpose — see the contact-matching rules before assuming a call and a contact will be linked.

Names inferred from a conversation are not published as trusted caller identity. caller.display_name is null until the name came from a source the account owner curated or imported.

Call lists are newest-first and use keyset pagination. The cursor is an opaque, versioned position containing the ordering timestamp and an ID tie-breaker.

  • Start without cursor.
  • If has_more is true, send next_cursor back exactly as returned.
  • Keep the same filters while walking every page.
  • A malformed or hand-edited cursor returns invalid_cursor.

Calls are immutable. Store the highest created_at you have processed. After an outage, query created_after from that watermark minus a safety overlap, walk every page, and deduplicate by call id.

The overlap is required because PostgreSQL assigns the timestamp when the write transaction starts, not when it becomes visible. One minute is ample for the current write path. The filter is inclusive, so seeing the boundary call twice is normal and safer than missing it.

The major API generation lives in the path: /v1. The beta contract is tracked as dated version 2026-10-01; explicit request-header pinning is planned but is not required or negotiated by the current endpoints.

Additive fields and event types may appear without a path change. Ignore fields and enum values you do not recognise. Removals, renames, type changes, or meaning changes require a dated contract release and migration notice.