Skip to content

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:

ConditionEffectSource
Tenant plan tierTranscription 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 eligibilityTranscription 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 configurationProvider 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/search

Operation searchTranscripts. It finds the recordings of the calling tenant whose transcript contains q, newest first.

ParameterRequiredRule
qyesThe word or phrase: 1 to 8 words.
fromyesRFC 3339 lower bound on the recording start, inclusive.
toyesRFC 3339 upper bound; at most 31 days after from.
agent_idnoOnly recordings of this agent (uuid).
campaign_idnoOnly recordings of this campaign (uuid).
limitno1 to 50, default 20; a larger value is capped, not refused.
cursornoOpaque 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 q are 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. (searchTranscripts description; 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, capped is true and next_cursor carries on from where it stopped. A page can therefore be short with capped: true; follow next_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.

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/original

Operation 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 the account_admin role. Anything else is 403 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.
  • 404 as 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:

DataComes 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 the account_admin role. Any other caller that sends include_pii=true gets 403 role_forbidden, not a redacted answer in silence.
  • Absent, false or "false": the redacted view. Any other value is 400.
  • Audit: every request with include_pii=true that 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/2 in v1_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}/analysis

Operation 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 ​

FieldMeaning
iduuid. Cite it as suggestion_id when typing the disposition.
call_attempt_idThe attempt.
sourcetranscript (a recording transcript) or ai_conversation (a voice-AI conversation).
summarySummary of the call, at most 1200 characters. Personal data; the schema says it is never logged.
suggested_codeA code ACTIVE in the tenant catalog, or null.
suggested_classClass of suggested_code in the catalog today: contacted, no_contact, busy, machine, bad_number, do_not_call, other, or null.
suggestion_noteWhy suggested_code is or is not set: suggested, model_none, not_in_catalog or inactive.
confidence_pctInteger 0 to 100 or null; the model's confidence, present only with a suggested code.
provider, model, prompt_versionWhat produced it.
created_atdate-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_id only records that the suggestion was shown and what the agent typed. The outcome is always the disposition in 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 is 404 and 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 or 0 means no cap, and then every paid provider is blocked; only the local provider, which costs 0, still runs. (config/runtime.exs, lib/dialer/inference/provider.ex.)
  • Provider. INFERENCE_TRANSCRIPTION_PROVIDER accepts local or deepgram; INFERENCE_ANALYSIS_PROVIDER accepts local or groq; 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 to local by 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.

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.