Receive every conversation as it finishes processing, or read across the corpus on demand. Both return the same enriched vCon, in the same shape, with the same field semantics - there is no richer internal view held back and no export path that lags what our own applications read.
An object exists once a conversation completes. It is quick, but do not design a flow that blocks on it mid-conversation, and do not treat the layer as something in the call path - it reads what your platform produced and cannot affect call setup, quality, or delivery.
Anything a tenant excluded from processing will not appear in a delivery or a query result. Conversations identified as personal rather than business are recognised and excluded from analysis. Very short exchanges with nothing substantive in them are skipped. Absence is defined behaviour, not a gap to reconcile.
Payment card data is redacted deterministically in the ingestion path, before any durable write and before any analysis. You will see a marker where a number was, never the number - and not because a field was hidden from you downstream.
Which tenant a request is for is derived from the token, never from a parameter in the request. There is no tenant identifier to get wrong, and no request shape that can reach across a boundary.
The push model delivers the complete enriched vCon when processing finishes, so you hold everything about a conversation in one delivery rather than assembling it from several calls.
A delivery carries the whole object. You do not receive a pointer and then fetch the content, which means no second round trip and no window where the notification exists and the data does not.
Every conversation carries a stable uuid, and source_call_id is preserved verbatim from the input. Key on either and a repeated delivery is safe rather than a duplicate record.
Acknowledge receipt and do your work asynchronously. A slow endpoint is indistinguishable from a broken one to any delivery mechanism, including ours.
Query conversations by customer, by time, by topic, or by what fired, over a versioned REST surface on a single host. Results page with a cursor.
| Concern | How it works |
|---|---|
| Authentication | A bearer credential on every request. The tenant is resolved from the credential, and scope is granted per capability rather than per key being all-powerful. |
| Host | One host for the whole surface, versioned in the path. There is no separate API hostname to configure and no split between a public and an internal base. |
| Paging | Cursor-based. A cursor survives new conversations arriving mid-iteration, which offset paging does not. |
| The transcript | Not part of the enriched vCon - it is an overlay and dialog[] is not populated. Fetch the transcript separately by source_call_id when you need it, which keeps one source of truth and keeps raw conversation content behind its own access control. |
| Errors | Conventional status codes with a readable body. A rejected request tells you which field was wrong rather than returning a generic failure. |
| Rate limits | Reported in response headers so you can back off deliberately rather than discover a ceiling by failing into it. |
Each analysis type on the object carries its own version, so the types move independently and your handling of one is never forced by a change to another.
New fields and new signals arrive as minor versions inside objects you already consume. Ignore what you do not recognise and next quarter cannot break this quarter.
No field or enum is renamed in place or repurposed to mean something new. If the meaning changes, so does the name and the version.
A breaking change is a new major version published alongside the current one, never a redefinition of the version you integrated against.
| Situation | What you get |
|---|---|
| A conversation the tenant excluded from processing | No object. It is not stored and not queryable, rather than stored and hidden. |
| A conversation identified as personal rather than business | Excluded from analysis and from reporting surfaces. Do not treat the gap as a processing failure. |
| A tenant that provides no reference data | A valid object with mapping_status: "unmapped". Association IDs are absent rather than guessed, and raw party addresses plus source_call_id are preserved so you can attribute it yourself. |
| An identifier that matches nothing in a configured inventory | That one ID is absent. A partial resolution reports mapping_status: "partial" rather than failing the object. |
| A signal this industry does not declare | Absent from signals entirely. This is not fired: false, and the difference matters when you aggregate. |
| A conversation with nothing substantive in it | A valid object on the skip path rather than an error or a silently dropped record. |
| The same conversation delivered twice | The same uuid and the same source_call_id. Key on either. |
The pattern across all of these: degrade to a valid object with an honest absence, never a hard error and never a fabricated value.
The object is documented field by field with a complete payload, so you can judge the fit before you talk to anyone. Access gets you credentials against a sandbox tenant and a corpus to build against.