Core concepts
Public objects
Section titled “Public objects”Audio and billing are not exposed; see Overview. Capacity is published as a resource.
An object has the same fields in a REST response and in an event’s
data.object.
Sources of truth
Section titled “Sources of truth”- Talki is the source of truth for Talki appointments. API writes follow the same rules as bookings made by phone and can be refused with the same conflicts.
- External calendars contribute busy time. Google and Outlook events can narrow the availability Talki reports. They constrain it and receive mirrors of it; they do not replace Talki’s appointment record.
- Availability is advisory. A window is true when computed, and another
caller can still take it. Only a successful
POST /v1/appointmentsholds the time.
Identifiers
Section titled “Identifiers”Talki IDs are opaque and case-sensitive. Do not parse them, infer a type from their shape, or generate one. Store the ID next to your own record, so webhook redelivery and feed recovery update rather than duplicate.
Contacts are matched to calls by normalised phone number. external_id and
system on contact upsert are your cross-references; they do not replace
Talki’s contact identity.
Locations
Section titled “Locations”An account works from one or more locations, exactly one of them the main
location. Calls, refused calls, unresolved requests, appointments, services,
resources, documents and caller rules carry the location_id they belong to, and closures carry it when
they apply at one location only. Lists of these records take ?location_id= to return one
location’s records, and the account, weekly hours and forwarding check are read
and written one location at a time. When multi_sede on the account is false,
there is only the main location. See
Work with locations.
Timestamps
Section titled “Timestamps”Public instants are RFC 3339 UTC and end in Z. Appointment writes must send
Z or an explicit offset; an offsetless value is rejected.
GET /v1/account returns the account’s IANA timezone for local dates and UI.
created_at and updated_at are display and reporting values, not sync
watermarks: a change can become visible after a later timestamp has already
been read. Resume from a feed cursor.
Lists and change feeds
Section titled “Lists and change feeds”GET /v1/calls has two modes:
- Without
sync, a newest-first list ordered bystarted_at, for a screen a person reads.next_cursoris null on the last page. - With
sync=true, a commit-ordered feed for backfill and recovery.next_cursoris your position and comes back on every non-empty page, including the last.
The list filters are refused alongside sync=true: created_after,
created_before, phone, contact_id, q, outcome, and a status other
than all. location_id is allowed with it. A cursor belongs to the
mode that issued it.
Appointments, contacts, services, resources, and unresolved requests
Section titled “Appointments, contacts, services, resources, and unresolved requests”These list endpoints are change feeds, apart from
a contact search. Start without cursor, upsert each
object by Talki ID, store every non-null next_cursor, and drive the loop from
has_more.
Keep the filters constant for the length of a walk, and do not filter out
cancelled appointments, inactive services or closed unresolved requests: those
states are precisely the changes a downstream copy needs. A walk filtered by
location_id is complete for that location, and its cursor is refused under
any other location_id, or without one.
A feed carries current state, not every intermediate edit, and deletions leave
no tombstone in it. A deleted contact sends contact.deleted, including one
deleted after twelve months without a call. A deleted service sends
service.deleted, and a deleted resource resource.deleted, each with the
object as it was. A call deleted with DELETE /v1/calls/{id} sends
call.deleted, which lists the unresolved requests deleted with it. Calls and
unresolved requests deleted after twelve months send no event. Walk each feed
in full periodically and reconcile the IDs that no longer come back; that also
catches a deletion whose event you missed.
A change to a resource’s unit classes or service links moves the resource itself in the feed.
Cursors are opaque and versioned, and belong to the endpoint and filters that
issued them. Do not decode one, edit one, or pass it to another endpoint or
filter. Each feed’s next_cursor names that feed, and the other feeds refuse it
with invalid_cursor. So are refused a cursor of the other kind (a list cursor
on a feed, or the reverse) and a change-feed cursor from another location_id
filter. A list cursor passed to another list is not always refused, and then
resumes from a meaningless position.
A feed cursor issued up to 2026-11-04 may name no feed. It keeps working until
2027-02-01, and any feed page you read replaces it with one that does. After that
date it gets invalid_cursor: drop it and walk the feed again from no cursor.
Find a contact by phone or tag
Section titled “Find a contact by phone or tag”GET /v1/contacts with phone or tag_id is a search rather than the feed, and
finds only the contacts the feed publishes:
phonetakes E.164, or a national number read as Italian. Anything else is refused with400 invalid_parameter.tag_idtakes aTag.id. An unknown or malformed one finds nothing.
With both, a contact must match both. Results are newest contact first, and
next_cursor is null on the last page, so a search gives you no position to
store. A feed cursor is refused by a search, and a search cursor by the feed,
with invalid_cursor. Sync from the unfiltered feed.
Documents
Section titled “Documents”GET /v1/documents is a newest-first list, not a change feed: its cursor pages
through the list as it stands. Keep a copy current with the document.*
events, and walk the whole list to rebuild it or to find deletions you missed;
see Manage the knowledge base.
Capacity
Section titled “Capacity”A resource is a finite thing the business books against: tables, rooms, chairs,
seats. A shared resource is one fungible pool of capacity units. A units
resource is a set of discrete sized units described by unit_classes, and there
capacity is the size of the largest single unit rather than a total — read
max_party_size for the largest booking it can hold.
A resource belongs to one location, and only services of that location draw on
it. A timed service draws on its location’s default resource (is_default), on
a resource of its own, or on resources the business built. A resource of its own
carries the service’s owner_service_id: it is the real per-service cap, and
it is deleted with the service.
Writing is much narrower than reading: name, and capacity on a shared
resource, plus creating a shared resource with POST /v1/resources, and
deleting a resource with DELETE /v1/resources/{id}. Unit classes and service links
are read-only, and a capacity below what the calendar has already committed is
refused with 409 capacity_below_committed. A delete is refused for the
default resource (409 resource_is_default) and for the only resource of a
service that still needs one (409 resource_required_by_service).
Unresolved requests
Section titled “Unresolved requests”When a caller asks something the assistant cannot answer, it records the question, says a person will get back to them, and the business works through the list. The object carries the question, a number to call back, and a status that closes: map it to a task.
A request moves in the feed again when its status or its answer changes. Sync without the
status filter, and use that filter only for a point-in-time view of the open
list.
Once a request is resolved, the general answer to its question can be taught to the assistant, which then gives it to every caller who asks; see Teach the assistant an answer.
The published question is the assistant’s own one-line record of what was
asked. The conversation itself is the call’s
transcript, under
transcripts:read.
Curated contacts
Section titled “Curated contacts”The contact feed publishes owner-curated, imported and API-written names only. An AI-inferred candidate is a pending contact until a person approves it. Names written through the API are trusted and are not overwritten by later transcript extraction.
Idempotent writes
Section titled “Idempotent writes”Every appointment create, reschedule, cancellation and restore, every
service, resource, closure, tag, caller-rule or document create, and teaching
the assistant (POST /v1/unresolved_requests/{id}/teach) require an
Idempotency-Key. Generate one per logical write
and reuse it only to retry the same method, path, query and body: a write can
commit and still lose its response, and the replay is what tells those two cases
apart.
Every other write accepts the header without requiring it. Once the first request
has a final answer, a repeated key with the same request replays the original
status and body and adds Idempotency-Replayed: true. An answer a retry could
change releases the key instead; see Retry rules. Reusing
a key for a different request returns 409 idempotency_key_reuse. Keys are at most 255 characters and are kept for 24
hours; after that, replay protection is no longer guaranteed.
Versioning
Section titled “Versioning”The major generation is /v1 and it never changes. The contract you are pinned
to is a dated version; the only one is 2026-10-01. See the
changelog.
Every credential carries its own pin, and that is what you are served when you send nothing: an API key pins on the key, an OAuth app pins on the app — never per installation, so one integration speaks one version for all of its customers — and a webhook endpoint pins on the endpoint.
Send Talki-Version: 2026-10-01 to override the pin for a single request. That
is how you rehearse an upgrade: one request at a time, against the sandbox,
before moving the pin for everybody. A response echoes the version it was
served at in the same header, so you can confirm it rather than infer it from
the payload. It is absent when the request was refused before a version was
chosen: 401, the 402/403 account refusals, unsupported_version itself,
ip_rate_limited, and every /oauth/* response.
A version the API does not serve is refused with 400 unsupported_version,
never downgraded; the message lists the accepted versions.
Fields and event types may be added without a path change, so ignore what you do not recognise. Removals, renames, type changes and meaning changes require a dated contract release and a migration notice.
