Appearance
Post-call intelligence
This guide covers what the platform derives from a finished call after the fact: searching the transcripts, reading the AI summary and suggested disposition of a call, and the spend brake that bounds the paid providers behind both. For the transcript itself, who may read it and how PII is redacted, see Recording transcripts, audio redaction and the screen-monitoring notice; this page does not repeat it.
Spanish version: Inteligencia post-llamada.
Field names, enums, limits and status codes come from the vendored openapi/dialer-v1.yaml (info.version: 0.105.0). Where the spec is silent, the statement cites a source file of core at tag v0.105.0 instead. The API Reference is the authority on shapes.
What governs availability
The spec defines no tenant flag for these routes. What decides whether data exists is, from core v0.105.0:
| Condition | Effect | Source |
|---|---|---|
| Tenant plan tier | Transcription and analysis are entitled only to the scale and enterprise tiers; any other tier, including an unknown or empty one, is not entitled. | lib/dialer/post_call/entitlement.ex |
| Recording eligibility | Transcription requires status available, a known duration within the configured minimum and maximum (defaults 10 and 3600 seconds), a terminal disposition of class contacted, a non-AI call and no previous transcript. | lib/dialer/post_call/eligibility.ex, config/runtime.exs (INFERENCE_TRANSCRIBE_MIN_SECS, INFERENCE_TRANSCRIBE_MAX_SECS) |
| Cell inference configuration | Provider choice and the monthly cap are set per cell by environment (INFERENCE_TRANSCRIPTION_PROVIDER, INFERENCE_ANALYSIS_PROVIDER, INFERENCE_MONTHLY_CAP_MICRO_USD). See Spend brake. | config/runtime.exs |
A call that does not qualify has no transcript and no analysis, and the read routes answer 404 (getRecordingTranscript, getCallAttemptAnalysis). There is no "pending" state in either response. Which tenants are on which tier, and which cell has which provider or cap, is not stated by the spec and is not stated here.
Search in transcripts
http
GET /v1/transcripts/searchOperation searchTranscripts. It finds the recordings of the calling tenant whose transcript contains q, newest first.
| Parameter | Required | Rule |
|---|---|---|
q | yes | The word or phrase: 1 to 8 words. |
from | yes | RFC 3339 lower bound on the recording start, inclusive. |
to | yes | RFC 3339 upper bound; at most 31 days after from. |
agent_id | no | Only recordings of this agent (uuid). |
campaign_id | no | Only recordings of this campaign (uuid). |
limit | no | 1 to 50, default 20; a larger value is capped, not refused. |
cursor | no | Opaque keyset cursor: the next_cursor of the previous page. |
from must be before to and the window may not exceed 31 days; otherwise 400.
bash
curl -sG "$API/v1/transcripts/search" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "q=payment promise" \
--data-urlencode "from=2026-10-01T00:00:00Z" \
--data-urlencode "to=2026-10-08T00:00:00Z"How matching works
- The words of
qare looked up in a blind index: a keyed hash with the tenant inside, so the same word hashes differently in two tenants and no word is stored in the clear. Only the candidate segments of the candidate recordings are decrypted, and a phrase is verified on the decrypted text. (searchTranscriptsdescription;lib/dialer/post_call/term_search.ex.) - A phrase matches only when its words appear in a row inside one segment. Matching is exact on the normalised word: case, accents and punctuation are ignored; there is no stemming and no synonyms.
- A call examines at most 100 candidate recordings and 500 segments. When that cap stops the search before the page is full,
cappedistrueandnext_cursorcarries on from where it stopped. A page can therefore be short withcapped: true; follownext_cursor.
Response
TranscriptSearchPage has results, next_cursor and capped. Each result has recording_id, started_at, agent_id and campaign_id (both may be null) and matches. Each match has seq, channel (0 or 1), start_ms, end_ms and fragment: the segment text around the match, with about 40 characters of context on each side.
Who can search, and what is audited
The gate is the one of the single transcript: a dashboard login (ddu_) with a supervisor or admin seat, or an account_admin login without a seat. A dd_ key, a widget token and an agent seat are 403 role_forbidden.
Every search that runs appends an event in the same transaction: who, when, the hash of the query, the window, the filters, and how many recordings were examined and returned; never the text. If the event cannot be written there is no result and the answer is 503.
Status codes: 200, 400, 401, 403, 503.
Retention and legal hold
A recording whose audio was purged has no transcript and never appears in results; one under legal_hold does (searchTranscripts). The purge deletes the transcript with the audio, and a recording under legal_hold keeps its transcript (getRecordingTranscript). The hold is set or lifted with PUT /v1/recordings/{id}/legal-hold (setRecordingLegalHold), tenant-management scope: a dd_ key or an account_admin login; a member login gets 403 role_forbidden (core#2438), and reading is unchanged. Retention is the tenant recording policy (setRecordingPolicy); changing it never re-dates existing recordings.
The original (unredacted) transcript
http
POST /v1/recordings/{id}/transcript/originalOperation readRecordingTranscriptOriginal. The method is POST, not GET: the default view GET /v1/recordings/{id}/transcript has the PII redacted (redacted: true), and this route is the only way to the original text (redacted: false).
- Caller: a
dd_tenant key or a dashboard login with theaccount_adminrole. Anything else is403 role_forbidden. - Body:
reason, required, non-empty, at most 500 characters. - The actor, the reason and the segment count (never the text) are written to an append-only access log before the text is returned. If the row cannot be written, no text leaves and the answer is
503. 404as in the default view.
Status codes: 200, 400, 401, 403, 404, 503. The RecordingTranscript shape and the redaction rules are in the transcripts guide.
AI conversation transcripts: redacted by default
http
GET /v1/ai/conversations/{id}Operation getAiConversation. It returns a voice-AI conversation with its transcript and outcome. The text of every transcript event comes with the PII of the closed catalog redacted; role, at and policy_version are unchanged.
This behaviour is in core main (core#2462, 2026-10-23) and is not yet in the vendored spec (info.version: 0.128.0), whose getAiConversation lists neither the include_pii parameter nor a 403; the statements below cite lib/dialer/pii.ex, lib/dialer/api/render.ex and lib/dialer/api/v1_router.ex of core. The API Reference will show them when the spec is refreshed.
What is redacted
The catalog is closed and fixed by the platform: payment card number, social security number, bank account and passwords/PINs. Nothing else is touched: a phone number or an agent id comes out as written. The mask is:
| Data | Comes out as |
|---|---|
| Card number (13 to 19 digits that pass the Luhn check) | ****-****-****-1234 (last four) |
| Social security number | ***-**-1234 (last four) |
Bank account (IBAN, CLABE, or digits after a label such as account or routing) | ****1234 (last four) |
Value after PIN, CVV, password, clave and similar labels | [REDACTED] (nothing is kept) |
Detection is deterministic rules with check digits, not a model, so it is a net and not a classifier: a card glued to another number by a space is not masked, and a long run of digits that passes the check by chance is (lib/dialer/pii.ex, section "Límites conocidos").
Asking for the original: include_pii
Add include_pii=true to the query to get the text as stored.
- Caller: a
dd_tenant key or a dashboard login with theaccount_adminrole. Any other caller that sendsinclude_pii=truegets403 role_forbidden, not a redacted answer in silence. - Absent,
falseor"false": the redacted view. Any other value is400. - Audit: every request with
include_pii=truethat finds the conversation records who asked and for which tenant, as a telemetry event and a structured log line. Unlike the original transcript of a recording, no reason is asked for, no row is written, and nothing waits on the write: the audit is the event and the log line (core#2462, commit message). - The same rule and the same role are those of the exports that take
include_pii(pii_mode/2inv1_router.ex).
What the voice-AI runtime already masked
What the consumer said aloud (card, social security number, account) is masked by the voice-AI runtime before the conversation event is emitted, so core stores it masked. include_pii=true returns the original of what was stored: for those spoken data, the masked text. The AI answers a fixed sentence and offers a person when a handoff is available (voice-ai ADR 17, Module 22, §9).
Status codes added to this route: 400 and 403 on include_pii; the rest are those of the spec.
Call summary and suggested disposition
http
GET /v1/call_attempts/{id}/analysisOperation getCallAttemptAnalysis. It returns the summary of one finished call and the disposition the AI suggests. It is a suggestion, never an outcome: nothing is applied.
Fields of analysis
| Field | Meaning |
|---|---|
id | uuid. Cite it as suggestion_id when typing the disposition. |
call_attempt_id | The attempt. |
source | transcript (a recording transcript) or ai_conversation (a voice-AI conversation). |
summary | Summary of the call, at most 1200 characters. Personal data; the schema says it is never logged. |
suggested_code | A code ACTIVE in the tenant catalog, or null. |
suggested_class | Class of suggested_code in the catalog today: contacted, no_contact, busy, machine, bad_number, do_not_call, other, or null. |
suggestion_note | Why suggested_code is or is not set: suggested, model_none, not_in_catalog or inactive. |
confidence_pct | Integer 0 to 100 or null; the model's confidence, present only with a suggested code. |
provider, model, prompt_version | What produced it. |
created_at | date-time. |
A suggestion of class do_not_call is returned as such, and a client must not preselect it (getCallAttemptAnalysis, CallAnalysis).
Who can read it
The agent that owns the attempt (a ddu_ login whose person is linked to the seat, or the ddw_ token bound to that agent), and a supervisor or admin. A dd_ key is a service and is 403 role_forbidden, as is a login with no seat and no supervisor role.
404 when the attempt has no analysis in this tenant: it belongs to another agent or tenant, it was never analysed (not eligible, no entitlement, or still pending), its audio was purged (the purge deletes the analysis with it), or the id is malformed. A client treats 404 as "no suggestion yet" and keeps the manual typing. Status codes: 200, 401, 403, 404.
The analysis of a human call is built from the redacted transcript view; that of an AI call from the conversation events (lib/dialer/post_call/analyze_job.ex). Eligibility is the tier entitlement above; the analysis exists only when the call produced text.
Confirming it: suggestion_id
The agent confirms by typing the disposition with POST /v1/call_attempts/{id}/disposition (operation recordDisposition, see the API Reference), optionally citing suggestion_id with the analysis id:
json
{
"disposition": "promise_to_pay",
"suggestion_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"promise": { "amount_cents": 12500, "currency": "USD", "promised_date": "2026-07-21" }
}suggestion_idonly records that the suggestion was shown and what the agent typed. The outcome is always thedispositionin the body, never the suggested one, and the suggestion changes nothing about the retry policy.- A malformed id is
400. An id that is not this attempt's analysis is404and nothing is appended. - The same suggestion cited twice records one adoption.
In core the adoption row keeps the suggested code (null if none) and the code the agent typed, without PII (lib/dialer/post_call/analysis_adoption.ex).
Spend brake (fail-closed)
The paid providers behind transcription and analysis are bounded by a monthly spend cap. The spec does not describe this brake (the OpenAPI document has no spend-cap field for these routes), so everything in this section is from core v0.105.0.
- Cap. The monthly cap is an integer in micro-USD from
INFERENCE_MONTHLY_CAP_MICRO_USD, set per cell. Absent, empty, non-numeric or0means no cap, and then every paid provider is blocked; only thelocalprovider, which costs 0, still runs. (config/runtime.exs,lib/dialer/inference/provider.ex.) - Provider.
INFERENCE_TRANSCRIPTION_PROVIDERacceptslocalordeepgram;INFERENCE_ANALYSIS_PROVIDERacceptslocalorgroq; any other value stops the node from starting. When absent they resolve to the paid provider, which the brake blocks without a cap. They never fall back tolocalby omission. - Reserve before spending. Each job asks the brake for a reservation before calling a provider. Only
{:ok, reserve_id}authorises the call. A denial (cap_unset,price_unknown,cap_reached,local_priced) ends the job without spending, and it is not re-queued. A ledger that cannot be read fails that attempt without spending, and the job is retried, up to 5 attempts (lib/dialer/inference/brake.ex,lib/dialer/inference/ledger.ex,lib/dialer/post_call/transcribe_job.ex,lib/dialer/post_call/analyze_job.ex). - Fail-closed on the ledger. A connection error, lock timeout, function exception or an answer that is not understood is treated as "do not run"; an unknown denial reason also blocks (
ledger.ex). - Fail-closed on the provider. If the speech or analysis service answers with a different provider than the one reserved, core stores nothing, releases the reservation and cancels without retry (
transcribe_job.ex,analyze_job.ex). Transcription also fails closed if PII cannot be redacted: nothing is stored. - Tier. An unknown tier has no entitlement and is not spent on (
entitlement.ex).
For an API client the visible effect is the same in every case: there is no transcript or analysis and the read routes answer 404.
Not covered here
- Whether a given tenant or cell currently has a non-zero cap, a paid provider or a qualifying tier. None of that is exposed by the spec.
- The transcript shape, redaction and original audio: see the transcripts guide.