#Calls Analytics

29 min read

Retrieve call records, transcripts, sentiment analysis, and export call data. Protected endpoints accept either X-API-Key or Authorization: Bearer <jwt> authentication and return data scoped to the resolved AuthContext.

Production API: https://api.aidial.ai

This page omits environment-specific host names, internal topology, and deployment details.

#Authentication And Scoping

Authentication is resolved in this order:

  1. X-API-Key
  2. Authorization: Bearer <jwt>

If both headers are present, the API key is used. Missing or invalid authentication returns 401 Unauthorized.

Bearer authentication is for AiDial Portal sessions. Bearer validation requires ZITADEL_ISSUER, ZITADEL_AUDIENCE, and ZITADEL_JWKS_URL; these settings are required and have no fallback defaults. Bearer tenant assignment is resolved from portal_users and portal_user_client_assignments.

Both authentication modes resolve to client_id, data_env, scope, and principal metadata. Analytics and call data are routed by the strict (client_id, data_env) mapping in the tenant routing map; metadata, authentication, portal assignment, project configuration, export state, and audit records use the local API metadata store on the API host. A missing tenant route is server misconfiguration and returns 500 Internal Server Error. If the routed data host, tunnel, or database is unavailable, the API returns 503 Service Unavailable.

PII-sensitive fields respect the client-level PII flags in aidial_clients. Phone and transcript decryption goes through server-side decryption pathways when plaintext access is permitted. If PII permissions are missing, permission lookup fails, or decryption fails, the API fails closed by redacting or omitting PII. Raw encrypted database fields such as *_encrypted, ciphertext, wrapped keys, and nonces are not response fields.

#GET /v1/calls/summary

Returns aggregate call statistics for your organisation.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
start_datequerystringNoStart date (inclusive). Accepts YYYY-MM-DD or ISO 8601
end_datequerystringNoEnd date (inclusive). Accepts YYYY-MM-DD or ISO 8601
project_idquerystringNoAll projectsFilter by project
solution_idquerystringNoAll solutionsFilter by solution

#Response

FieldTypeDescription
total_callsintegerTotal number of calls
completed_callsintegerCalls completed successfully
missed_callsintegerCalls that were missed
failed_callsintegerCalls that failed
total_duration_secondsintegerNon-null combined duration of all scoped calls; returns 0 when no calls match
avg_duration_secondsnumber or nullAverage call duration
total_cost_usdnumber or nullTotal cost in USD
avg_cost_usdnumber or nullAverage cost per call in USD
currencystring or nullLocal currency code (e.g. AUD, USD) when all matching rows share one non-null currency and complete local cost values; otherwise null
total_cost_localnumber or nullTotal cost in the local currency; null when currency is null
avg_cost_localnumber or nullAverage cost per call in the local currency; null when currency is null
human_escalation_countintegerNumber of calls escalated to a human
sentiment_distributionobjectCounts by overall sentiment (positive, neutral, negative)
caller_sentiment_distributionobjectCounts by caller sentiment (positive, neutral, negative). For this aggregate the values currently mirror sentiment_distribution because the summary query derives both from sentiment_overall. Use GET /v1/calls/sentiment for a distinct caller-sentiment breakdown
disconnection_reason_distributionobjectNormalized Aidial API disconnection reason counts: caller_hangup, agent_or_system_end, transferred, missed, failed, unknown
avg_ai_response_secondsnumber or nullAverage AI first-response time

Display guidance: prefer the local-currency fields only when currency is non-null; otherwise display the USD fields explicitly labelled as USD rather than guessing a currency. The USD fields remain unchanged for all callers.

Disconnection reason guidance:

  • disconnection_reason_distribution is a normalized Aidial API taxonomy, not raw LiveKit, SIP, telephony, or provider metadata.
  • Counts use the same resolved client_id, data_env, date, project_id, and solution_id scope as the rest of the summary response.
  • The fallback order is transfer evidence, missed, failed, explicit caller hangup, explicit agent/system end, then unknown.
  • Transfer evidence comes from persisted ai_calls.call_metadata.transfer.attempts named targets (target, destination_key, or destination_name) in keyed-object or array-shaped attempt metadata, or transfer resolution metadata. Direct-call and in-progress transfer markers are ignored.
  • call_status = completed by itself is not enough evidence for caller_hangup or agent_or_system_end; missing, legacy, blank, or unrecognized metadata counts as unknown.
  • Empty scoped results return all six buckets as 0; non-empty distributions sum to total_calls.
  • A hidden legacy partner passthrough is also served at /v1/calls/summary by the partner call-control router (excluded from the OpenAPI schema and registered ahead of the public summary route). It delegates to the same handler and returns the same normalized response model after the caller's target-client authority is resolved.

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/summary?start_date=2026-03-01&end_date=2026-03-15" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE"

Response (200 OK):

JSON
{
  "total_calls": 142,
  "completed_calls": 120,
  "missed_calls": 15,
  "failed_calls": 7,
  "total_duration_seconds": 28400,
  "avg_duration_seconds": 200,
  "total_cost_usd": 42.60,
  "avg_cost_usd": 0.30,
  "currency": "AUD",
  "total_cost_local": 64.39,
  "avg_cost_local": 0.45,
  "human_escalation_count": 8,
  "sentiment_distribution": {"positive": 85, "neutral": 42, "negative": 15},
  "caller_sentiment_distribution": {"positive": 85, "neutral": 42, "negative": 15},
  "disconnection_reason_distribution": {
    "caller_hangup": 82,
    "agent_or_system_end": 25,
    "transferred": 13,
    "missed": 15,
    "failed": 7,
    "unknown": 0
  },
  "avg_ai_response_seconds": 1.2
}

#GET /v1/calls/outcomes

Returns the Rev 6 dashboard conversation outcome aggregate for one scoped project and date range.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
project_idquerystringYesSelected project. Out-of-scope projects return non-enumerating 404
start_datequerystringYesStart date or ISO 8601 boundary
end_datequerystringYesEnd date or ISO 8601 boundary
client_idquerystringNoAuthenticated clientTarget client id. Partner and internal bearer callers may use an assigned/in-scope client; unauthorized targets return non-enumerating 404
solution_idquerystringNoAll solutionsOptional solution filter

#Source And Empty-State Semantics

  • Category ids and labels come only from the selected project's API-managed dashboard outcome_enum.
  • Calls are counted from routed tenant call records scoped by the resolved target client_id, authenticated data_env, project_id, and date range.
  • Primary outcome derivation uses persisted evidence only: exact in-taxonomy values from booking_result, transfer_result, or call_resolution_status, then capability/status rollups only when the project taxonomy explicitly declares a known outcome family label key.
  • Categories not present in the project taxonomy are not returned, even when matching call evidence exists.
  • Missing taxonomy or unsupported data returns categories: [], classified_count: 0, and unclassified_count equal to the scoped total.
  • The API does not infer families from English taxonomy ids or labels. Additional SQL evidence keys require a documented project taxonomy extension contract before they are added.
  • The response never includes caller PII, transcript text, summaries, raw analysis_data, prompt text, encrypted fields, recordings, API keys, bearer tokens, or secret-backend references.

#Empty / Unknown Rendering Guidance

  • When total_conversations == 0, render the outcomes panel as an empty range state. Do not render a donut wedge.
  • When total_conversations > 0 && classified_count == 0, render an unknown taxonomy/evidence state. The donut may show a single portal-owned "Unclassified" wedge using a portal i18n key such as outcomes.unclassified.title.
  • When total_conversations > 0 && unclassified_count > 0, include an "Unclassified" wedge or equally visible coverage indicator so the classified donut does not imply full coverage. The unclassified label is portal-owned i18n, not API-provided taxonomy text.
  • When categories[].capability is present, clients may surface a follow-up affordance tied to that bucket. Absence of capability must not be treated as "capability disabled"; it only means the taxonomy entry was not mapped to an enabled booking or transfer capability hint.
  • source and taxonomy_revision are contract identifiers for developers and diagnostics. Do not show them directly as customer-facing copy; map them to localized labels or hide them.

#Response

FieldTypeDescription
project_idstringSelected project id
start_datestringEchoed selected start boundary
end_datestringEchoed selected end boundary
total_conversationsintegerAll scoped conversations in the selected range
classified_countintegerSum of returned bucket counts
unclassified_countintegerScoped calls not matched to the project taxonomy
taxonomy_revisionstring or nullDashboard metadata source revision
sourcestringproject_taxonomy_derived_call_evidence
categoriesarrayOrdered non-zero taxonomy buckets
categories[].idstringProject taxonomy id
categories[].labelstringProject taxonomy label
categories[].label_keystring or nullOptional stable portal i18n key for known outcome families; null for custom taxonomy entries
categories[].countintegerBucket count
categories[].percentagenumberShare of total_conversations
categories[].capabilitystring, optionalbooking or transfer when the project metadata marks that capability enabled

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/outcomes?project_id=proj_example_001&start_date=2026-03-01&end_date=2026-03-15" \
  -H "Authorization: Bearer PORTAL_SESSION_JWT"

Response (200 OK):

JSON
{
  "project_id": "proj_example_001",
  "start_date": "2026-03-01",
  "end_date": "2026-03-15",
  "total_conversations": 142,
  "classified_count": 130,
  "unclassified_count": 12,
  "taxonomy_revision": "published:7:safe",
  "source": "project_taxonomy_derived_call_evidence",
  "categories": [
    {
      "id": "booked_appointment",
      "label": "Booked appointment",
      "label_key": "outcomes.booking.title",
      "count": 38,
      "percentage": 26.76,
      "capability": "booking"
    }
  ]
}

#GET /v1/calls/dashboard/privacy-aggregates

Returns privacy-safe Rev 6 dashboard data for the recent calls list and caller mix card.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
project_idquerystringYes-Selected project. Out-of-scope projects return non-enumerating 404
start_datequerystringYes-Start date or ISO 8601 boundary
end_datequerystringYes-End date or ISO 8601 boundary
client_idquerystringNoAuthenticated clientTarget client id. Partner and internal bearer callers may use an assigned/in-scope client; unauthorized targets return non-enumerating 404
solution_idquerystringNoAll solutionsOptional solution filter
recent_limitqueryintegerNo5Number of recent call summaries to return. Minimum 1, maximum 10

#Source And Privacy Semantics

  • Recent-call outcomes use the same project_taxonomy_derived_call_evidence classifier as GET /v1/calls/outcomes.
  • Recent-call rows are intentionally small: call id, start time, duration, normalized sentiment, safe outcome label, and entitlement-driven detail availability only.
  • Caller mix is an aggregate only. The API may use server-side caller identity evidence to count unique, new, and returning callers over a 30-day lookback, but grouping uses a tenant-scoped HMAC-SHA-256 key inside the routed database and no identity value or hash is returned.
  • caller_mix.status = available with reason_code = ready includes aggregate counts, percentages, total calls, and average calls per caller.
  • caller_mix.status = available with reason_code = no_calls returns zero metrics for an empty range when matching is supported.
  • caller_mix.status = unavailable with reason_code = unsupported means the project has no supported privacy-safe caller mix capability.
  • caller_mix.status = unavailable with reason_code = identity_unavailable means calls exist but safe matching evidence is unavailable or only partially covers the selected calls.
  • caller_mix.status = unavailable with reason_code = below_k means identity evidence exists but the unique caller population, new caller bucket, or returning caller bucket is below the minimum caller-mix threshold of 5; identity-derived metrics are omitted.
  • Unclassified recent-call outcomes carry label_key = "outcomes.unclassified.title" so the portal can localize the safe fallback label.
  • detail_available is true only for the owning tenant's client_admin sessions and assigned partner_admin sessions; read-only, internal, API-key, or partial-scope callers receive false.
  • The API emits dashboard_privacy_aggregates_read audit events for successful reads and non-enumerating project-scope denials. Audit metadata records actor client, target client, project, and reason code, and excludes caller identity, HMAC output, and salts.
  • The response never includes caller names, full or masked phone numbers, CRM patient labels, caller ids, stable identity hashes, transcript text, summaries, recordings, raw analysis_data, encrypted fields, API keys, bearer tokens, secrets, or secret-backend references.

#Response

FieldTypeDescription
project_idstringSelected project id
start_datestringEchoed selected start boundary
end_datestringEchoed selected end boundary
sourcestringproject_taxonomy_derived_call_evidence
recent_callsarrayNewest-first safe recent call summaries
recent_calls[].call_idstringNon-PII call identifier used for navigation
recent_calls[].started_at_utcstringCall start timestamp
recent_calls[].duration_secondsinteger or nullCall duration when available
recent_calls[].sentimentstringpositive, neutral, upset, or unclassified
recent_calls[].outcomeobjectSafe outcome with id, label, optional label_key, and state
recent_calls[].detail_availablebooleanTrue only when the caller has call-detail entitlement for the scoped project
caller_mixobjectCaller mix availability state and aggregate metrics
caller_mix.statusstringavailable or unavailable
caller_mix.reason_codestringMachine-readable availability reason: ready, unsupported, identity_unavailable, below_k, or no_calls
caller_mix.lookback_daysintegerCaller-mix lookback window in days; currently 30
caller_mix.metricsobject or nullAggregate counts and percentages when ready or empty-supported

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/dashboard/privacy-aggregates?project_id=proj_example_001&start_date=2026-03-01&end_date=2026-03-15&recent_limit=5" \
  -H "Authorization: Bearer PORTAL_SESSION_JWT"

Response (200 OK):

JSON
{
  "project_id": "proj_example_001",
  "start_date": "2026-03-01",
  "end_date": "2026-03-15",
  "source": "project_taxonomy_derived_call_evidence",
  "recent_calls": [
    {
      "call_id": "LK-room-001",
      "started_at_utc": "2026-03-15T02:10:00Z",
      "duration_seconds": 184,
      "sentiment": "positive",
      "outcome": {
        "id": "booked_appointment",
        "label": "Booked appointment",
        "label_key": "outcomes.booking.title",
        "state": "classified"
      },
      "detail_available": true
    }
  ],
  "caller_mix": {
    "status": "available",
    "reason_code": "ready",
    "lookback_days": 30,
    "metrics": {
      "total_calls": 70,
      "unique_callers": 52,
      "new_callers": 36,
      "returning_callers": 16,
      "new_callers_percent": 69.23,
      "returning_callers_percent": 30.77,
      "avg_calls_per_caller": 1.35
    }
  }
}

#GET /v1/calls/list

Returns a paginated list of call records with optional filtering and sorting.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
start_datequerystringNoStart date (inclusive). YYYY-MM-DD or ISO 8601
end_datequerystringNoEnd date (inclusive). YYYY-MM-DD or ISO 8601
project_idquerystringYesSelected project. Missing project id returns 400; out-of-scope projects return non-enumerating 404
solution_idquerystringNoAll solutionsFilter by solution
statusquerystringNoAllFilter by call status (completed, missed, failed)
sentimentquerystringNoAllFilter by sentiment (positive, neutral, negative)
resolutionquerystringNoAllFilter by resolution status
call_typequerystringNoAllFilter by call direction/type (inbound, outbound)
attention_neededquerybooleanNoAllFilter calls requiring owner attention
booking_resultquerystringNoAllFilter by booking result
transfer_resultquerystringNoAllFilter by transfer result
follow_up_statusquerystringNoAllFilter by follow-up status
sms_statusquerystringNoAllFilter by SMS delivery status
email_statusquerystringNoAllFilter by email delivery status
min_duration_secondsqueryintegerNoMinimum duration in seconds
max_duration_secondsqueryintegerNoMaximum duration in seconds. Must be greater than or equal to min_duration_seconds
callerquerystringNoSearch by caller identifier
agentquerystringNoSearch by agent name
summary_searchquerystringNoPrivacy-safe search over redaction-safe summary text and call metadata. Maximum 160 characters. Phone numbers, email addresses, Medicare-like identifiers, and DOB-shaped values are rejected with 400 SEARCH_QUERY_PII_NOT_ALLOWED before any database access. The raw query text is never logged, audited, or echoed in error envelopes; accepted searches emit a calls.search audit event with a SHA-256 fingerprint and matched count only. Transcript text, transcript ciphertext, encrypted columns, recordings, and secret-backend artifacts are never searched.
primary_outcomequerystringNoFilter to rows whose API-owned safe outcome id matches this value. Slug shape (^[a-z0-9_-]+$, max 80 characters). Values come from the selected project's outcome taxonomy as returned by /v1/calls/outcomes for the same project. The reserved value unclassified selects rows whose primary_outcome.state is unclassified OR unknown_taxonomy. Unknown ids return 400 INVALID_PRIMARY_OUTCOME and the response body does NOT echo the raw value.
sort_byquerystringNostarted_at_descSort order. Options: started_at_desc, started_at_asc, duration_desc, duration_asc, cost_desc, cost_asc
pagequeryintegerNo1Page number (1-based, minimum 1)
page_sizequeryintegerNo50Requested results per page (1-500). Effective maximum is 100 unless include_transcript=true, which allows 500
include_transcriptquerybooleanNofalseInclude pre-redacted transcript messages in each call

#Response

FieldTypeDescription
callsarrayList of call record objects
calls[].call_idstringUnique call identifier
calls[].client_idstringClient identifier
calls[].project_idstringProject identifier
calls[].solution_idstringSolution identifier
calls[].started_at_utcstring (ISO 8601)Call start timestamp
calls[].ended_at_utcstring (ISO 8601) or nullCall end timestamp
calls[].duration_secondsnumber or nullCall duration
calls[].call_statusstringStatus (completed, missed, failed)
calls[].call_directionstring or nullDirection (inbound, outbound)
calls[].channelstring or nullCommunication channel
calls[].from_numberstring or nullCaller number (redacted based on PII config)
calls[].to_numberstring or nullCalled number
calls[].summarystring or nullAI-generated call summary. Returned as null whenever summary_status is not ok
calls[].summary_statusstringBackend-owned summary availability marker. One of ok, redacted_failed, or unavailable
calls[].sentiment_label_extensionstring or nullBackend-derived escalation extension label. Set to escalation_suggested only when backend-owned human-escalation evidence is present; null otherwise. Frontend sentiment, duration, or score heuristics do not populate this field
calls[].transfer_destinationobject or nullRedaction-safe current transfer destination display. When present: `{ "label": string, "destination_type": "internal_extension" \"external_number" \"queue" \"agent", "revision_id": string }. The label is resolved from the named runtime transfer key to the current active Portal destination label, so historical rows can change when a destination is renamed. null` when the call did not transfer to a named destination, the destination is deleted/inactive, lookup fails, or the current label fails the safety check
calls[].primary_outcomeobjectAPI-owned safe outcome label derived from the project's outcome taxonomy. Same id-space and label_key vocabulary as /v1/calls/outcomes. Shape: `{ "id": string, "label": string \null, "label_key": string \null, "state": "classified" \"unclassified" \"unknown_taxonomy" }. classified = row evidence matched a taxonomy bucket. unclassified = project taxonomy present but the row did not match. unknown_taxonomy = project taxonomy is missing or empty (the id is the reserved "unclassified", label is null, label_key is "outcomes.unclassified.title"`).
calls[].call_resolution_statusstringResolution status; defaults to unavailable when not populated
calls[].booking_resultstringBooking result status; defaults to unavailable when not populated
calls[].transfer_resultstringTransfer result status; defaults to unavailable when not populated
calls[].follow_up_statusstringFollow-up status; defaults to unavailable when not populated
calls[].sms_statusstringSMS delivery status; defaults to unavailable when not populated
calls[].email_statusstringEmail delivery status; defaults to unavailable when not populated
calls[].sentiment_overallstring or nullOverall sentiment
calls[].caller_sentimentstring or nullCaller sentiment
calls[].message_countinteger or nullNumber of transcript messages
calls[].human_escalation_neededbooleanWhether human escalation was triggered
calls[].cost_usdnumber or nullCall cost in USD
calls[].llm_token_usedintegerLLM tokens consumed
calls[].stt_minutes_processednumberSpeech-to-text minutes
calls[].tts_characters_synthesizedintegerText-to-speech characters
calls[].llm_providerstring or nullLLM provider name
calls[].llm_modelstring or nullLLM model name
calls[].transcript_messagesarray or nullTranscript (only when include_transcript=true)
paginationobjectPagination metadata
pagination.pageintegerCurrent page
pagination.page_sizeintegerRequested page size
pagination.total_recordsintegerTotal records matching the filters
pagination.total_pagesintegerTotal pages based on the requested page size
scopeobjectResponse-level scope metadata
scope.empty_state_reasonstring or nullReason the list is empty. no_calls_in_period for default/date-only empty results, filtered_out when non-default filters narrow an otherwise non-empty range to zero, or null when calls are present

Pagination note: Results use offset-based pagination. If new records are inserted between page requests, results may shift. For consistent snapshots, use narrow date ranges.

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/list?project_id=proj_example_001&page=1&page_size=2&start_date=2026-03-01&sort_by=started_at_desc" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE"

Response (200 OK):

JSON
{
  "calls": [
    {
      "call_id": "call_00000000-0000-4000-a000-000000000001",
      "client_id": "client_example_001",
      "project_id": "proj_example_001",
      "solution_id": "sol_example_001",
      "started_at_utc": "2026-03-15T09:30:00Z",
      "ended_at_utc": "2026-03-15T09:35:22Z",
      "duration_seconds": 322,
      "call_status": "completed",
      "call_direction": "inbound",
      "channel": "phone",
      "from_number": "+61 400 000 001",
      "to_number": "+61 2 0000 0001",
      "summary": "Caller requested an appointment booking for next Tuesday.",
      "summary_status": "ok",
      "sentiment_label_extension": null,
      "transfer_destination": {
        "label": "Front desk",
        "destination_type": "external_number",
        "revision_id": "trv_5f8c20a47b9b41acb2"
      },
      "primary_outcome": {
        "id": "booked_appointment",
        "label": "Booked appointment",
        "label_key": "outcomes.booking.title",
        "state": "classified"
      },
      "call_resolution_status": "resolved",
      "booking_result": "booked",
      "transfer_result": "unavailable",
      "follow_up_status": "sent",
      "sms_status": "unavailable",
      "email_status": "sent",
      "sentiment_overall": "positive",
      "caller_sentiment": "positive",
      "message_count": 12,
      "human_escalation_needed": false,
      "cost_usd": 0.48,
      "llm_token_used": 1250,
      "stt_minutes_processed": 5.37,
      "tts_characters_synthesized": 3200,
      "llm_provider": "openai",
      "llm_model": "gpt-4o",
      "transcript_messages": null
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 2,
    "total_records": 142,
    "total_pages": 71
  },
  "scope": {
    "empty_state_reason": null
  }
}

#GET /v1/calls/timeline

Returns time-series call metrics for charting.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
start_datequerystringNoStart date (inclusive). YYYY-MM-DD or ISO 8601
end_datequerystringNoEnd date (inclusive). YYYY-MM-DD or ISO 8601
project_idquerystringNoAll projectsFilter by project
solution_idquerystringNoAll solutionsFilter by solution
intervalquerystringNodayTime interval: day, week, or month

#Response

FieldTypeDescription
timelinearrayTime-series data points
timeline[].periodstring (ISO 8601)Start of the time bucket
timeline[].total_callsintegerTotal calls in this period
timeline[].completed_callsintegerCompleted calls
timeline[].missed_callsintegerMissed calls
timeline[].failed_callsintegerFailed calls
timeline[].total_duration_secondsnumber or nullTotal duration
timeline[].avg_duration_secondsnumber or nullAverage duration
timeline[].total_cost_usdnumber or nullTotal cost in USD
timeline[].human_escalation_countintegerHuman escalation count

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/timeline?start_date=2026-03-10&end_date=2026-03-15&interval=day" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE"

Response (200 OK):

JSON
{
  "timeline": [
    {
      "period": "2026-03-10T00:00:00Z",
      "total_calls": 18,
      "completed_calls": 15,
      "missed_calls": 2,
      "failed_calls": 1,
      "total_duration_seconds": 3600,
      "avg_duration_seconds": 200,
      "total_cost_usd": 5.40,
      "human_escalation_count": 1
    }
  ]
}

#GET /v1/calls/sentiment

Returns sentiment distribution across calls. Both start_date and end_date are required.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
start_datequerystringYesStart date (inclusive). Accepts YYYY-MM-DD or ISO 8601
end_datequerystringYesEnd date. Accepts YYYY-MM-DD or ISO 8601
project_idquerystringNoAll projectsFilter by project
solution_idquerystringNoAll solutionsFilter by solution

Date boundary semantics match /v1/calls/summary:

  • A YYYY-MM-DD end_date is inclusive of that whole UTC day.
  • An ISO 8601 datetime end_date is the exclusive UTC upper bound and is not expanded by an extra day.
  • A reversed range (start after end) returns 400; mixed date/datetime ranges are compared safely on UTC boundaries.
  • For an identical window and scope, the distribution total_calls aligns with /v1/calls/summary total_calls.

#Response

FieldTypeDescription
overallobjectOverall sentiment distribution
overall.positiveintegerPositive count
overall.neutralintegerNeutral count
overall.negativeintegerNegative count
overall.analyzedintegerTotal analysed
callerobjectCaller sentiment distribution (same structure)
agentobjectAgent sentiment distribution (same structure)
total_callsintegerTotal calls in range
start_datestringEchoed exactly as supplied (YYYY-MM-DD or ISO 8601)
end_datestringEchoed exactly as supplied (YYYY-MM-DD or ISO 8601)

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/sentiment?start_date=2026-03-01&end_date=2026-03-15" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE"

Response (200 OK):

JSON
{
  "overall": {"positive": 85, "neutral": 42, "negative": 15, "analyzed": 142},
  "caller": {"positive": 90, "neutral": 38, "negative": 14, "analyzed": 142},
  "agent": {"positive": 100, "neutral": 35, "negative": 7, "analyzed": 142},
  "total_calls": 142,
  "start_date": "2026-03-01",
  "end_date": "2026-03-15"
}

#GET /v1/calls/sentiment/timeline

Returns sentiment trend over time. Both start_date and end_date are required. The response includes dense time buckets for the requested interval; buckets with no matching calls return zero counts and zero percentages.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
start_datequerystringYesStart date (inclusive). Accepts YYYY-MM-DD or ISO 8601
end_datequerystringYesEnd date. Accepts YYYY-MM-DD or ISO 8601
project_idquerystringNoAll projectsFilter by project
solution_idquerystringNoAll solutionsFilter by solution
intervalquerystringNodayTime interval: day, week, or month

Date boundary semantics match /v1/calls/summary:

  • A YYYY-MM-DD end_date is inclusive of that whole UTC day.
  • An ISO 8601 datetime end_date is the exclusive UTC upper bound and is not expanded by an extra day. The dense bucket range still spans the requested start/end calendar window.
  • A reversed range (start after end) returns 400; mixed date/datetime ranges are compared safely on UTC boundaries.

#Response

FieldTypeDescription
timelinearrayTime-series sentiment data points
timeline[].periodstring (ISO 8601)Start of the time bucket
timeline[].total_callsintegerTotal scoped calls in this period, including calls without a sentiment value
timeline[].positive_countintegerPositive sentiment count
timeline[].neutral_countintegerNeutral sentiment count
timeline[].negative_countintegerCanonical backend field for negative sentiment count; the portal may label this bucket as "Upset"
timeline[].positive_percentnumberPositive percentage of calls with classified sentiment
timeline[].neutral_percentnumberNeutral percentage of calls with classified sentiment
timeline[].negative_percentnumberNegative percentage of calls with classified sentiment
start_datestringEchoed exactly as supplied (YYYY-MM-DD or ISO 8601)
end_datestringEchoed exactly as supplied (YYYY-MM-DD or ISO 8601)
intervalstringInterval used

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/sentiment/timeline?start_date=2026-03-10&end_date=2026-03-15&interval=day" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE"

Response (200 OK):

JSON
{
  "timeline": [
    {
      "period": "2026-03-10T00:00:00Z",
      "total_calls": 18,
      "positive_count": 12,
      "neutral_count": 5,
      "negative_count": 1,
      "positive_percent": 66.7,
      "neutral_percent": 27.8,
      "negative_percent": 5.6
    }
  ],
  "start_date": "2026-03-10",
  "end_date": "2026-03-15",
  "interval": "day"
}

#GET /v1/calls/provider-performance

Returns scoped provider and latency analytics for dashboard comparison views.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>) Scopes: client_staff, client_admin, client_manager, partner_admin, partner_user, aidial_admin, aidial_operator, read-only, or full-access

#Parameters

NameInTypeRequiredDefaultDescription
start_datequerystringYesInclusive ISO 8601 UTC datetime with timezone
end_datequerystringYesExclusive ISO 8601 UTC datetime with timezone. Must be after start_date and no more than 90 days later
timezonequerystringYesIANA timezone used for daily trend labels
client_idquerystringNoAuthenticated clientTarget client id. Partner and internal bearer callers may use an assigned/in-scope client; unauthorized targets return non-enumerating 404
project_idquerystringNoAll projectsOptional project filter
categoryquerystringNoRole defaultOne of perceived_response, stt, llm, or tts. client_staff may request only perceived_response
providerquerystringNoAll providersProvider filter for provider-visible scopes only: partner_admin, partner_user, aidial_admin, aidial_operator, read-only, or full-access. Not valid with category=perceived_response

#Response

FieldTypeDescription
generated_atstring (ISO 8601)Response generation time
rangeobjectEchoed UTC range, requested timezone, and granularity: "day"
visibilityobjectRole-derived projection rules, including provider-name visibility, provider-filter permission, safe-reference visibility, comparison projection, and allowed categories. API-key read-only and full-access scopes are provider-visible and are reported as partner_admin in visibility.role
filters.projectsarrayProject filter options visible in the scoped result set
filters.categoriesarrayCategory options allowed for the caller
filters.providersarrayProvider filter options. Empty when provider names are not visible
summaryobjectCounts for total calls, completed calls, valid metric samples, missing metrics, privacy exclusions, outliers, and cold-start outliers
comparisonsarrayCategory or provider comparison rows. Empty for client_staff, category-level for client_admin and client_manager, provider-level for provider-visible scopes where provider data exists
trendsarrayDaily latency trend rows by category
safe_call_referencesarray, optionalUp to 20 safe outlier call references. Present only for partner_admin, aidial_admin, and aidial_operator
methodologyobjectPercentile method, thresholds, and sparse-sample rules

The endpoint reads only allowlisted call timing and provider fields from routed tenant call records. It does not return transcripts, summaries, phone numbers, recording URLs, encrypted fields, API keys, bearer tokens, or secret-backend references.

Error semantics:

  • 400 INVALID_DATE_RANGE, DATE_RANGE_TOO_LARGE, INVALID_TIMEZONE, or INVALID_FILTER for invalid query input.
  • 404 NOT_AUTHORIZED for out-of-scope clients, projects, roles, categories, or provider filters.
  • 503 UPSTREAM_DATA_UNAVAILABLE when routed analytics data cannot be read.

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/provider-performance?start_date=2026-03-01T00:00:00Z&end_date=2026-03-15T00:00:00Z&timezone=Australia%2FBrisbane&project_id=proj_example_001&category=llm" \
  -H "Authorization: Bearer PORTAL_SESSION_JWT"

#GET /v1/calls/{call_id}

Returns scoped detail for a single call. Transcript and phone visibility depend on the authenticated role and client PII configuration.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>) Access: Scoped to the resolved tenant and project. Out-of-scope calls return non-enumerating 404

#Parameters

NameInTypeRequiredDefaultDescription
call_idpathstringYesUnique call identifier. The API looks up the call by LiveKit room SID or room name
include_original_transcriptquerybooleanNofalseWhen true, attempts to return the original transcript through the server-side transcript-decryption path if the caller and client PII flags permit it. The default response uses pre-redacted transcript messages

#Response

FieldTypeDescription
call_idstringUnique call identifier
client_idstringClient identifier
project_idstringProject identifier
solution_idstringSolution identifier
compliance_requiredbooleanWhether compliance handling was required for the call
compliance_jurisdictionstring or nullCompliance jurisdiction when available
started_at_utcstring (ISO 8601)Call start timestamp
ended_at_utcstring (ISO 8601) or nullCall end timestamp
duration_secondsnumber or nullCall duration
call_statusstringCall status
call_directionstring or nullDirection (inbound, outbound)
channelstring or nullCommunication channel
from_numberstring or nullCaller number, redacted based on role and PII configuration
to_numberstring or nullCalled number, redacted based on role and PII configuration
summarystring or nullAI-generated call summary. Returned as null whenever summary_status is not ok
summary_statusstringBackend-owned summary availability marker: ok, redacted_failed, or unavailable
sentiment_label_extensionstring or nullescalation_suggested only when backend-owned human-escalation evidence is present
transfer_destinationobject or nullRedaction-safe current transfer destination display. Same shape and rules as documented on /v1/calls/list
primary_outcomeobjectAPI-owned safe outcome label. Same shape and rules as documented on /v1/calls/list ({ id, label, label_key, state }).
sentiment_overallstring or nullOverall sentiment
caller_sentimentstring or nullCaller sentiment
agent_sentimentstring or nullAgent sentiment
message_countinteger or nullNumber of transcript messages
transcriptarray or nullTranscript messages, each with speaker, timestamp, text, and optional sentiment. May be redacted or omitted based on PII configuration and decryption outcome
call_resolution_statusstring or nullResolution status
ai_first_response_secondsnumber or nullAI first-response time
human_escalation_neededbooleanWhether human escalation was needed
human_intervention_countintegerNumber of human interventions
cost_usdnumber or nullCall cost in USD
cost_localnumber or nullCall cost in local currency
cost_local_currencystring or nullLocal currency code
cost_breakdownobject or nullDetailed cost breakdown when available
llm_token_usedintegerLLM tokens consumed
stt_minutes_processednumberSpeech-to-text minutes
tts_characters_synthesizedintegerText-to-speech characters
cloud_providerstringCloud provider
cloud_regionstringCloud region
llm_providerstring or nullLLM provider
llm_modelstring or nullLLM model
stt_providerstring or nullSTT provider
tts_providerstring or nullTTS provider
tts_voicestring or nullTTS voice
telephony_providerstring or nullTelephony provider
telephony_call_idstring or nullProvider call identifier
livekit_room_namestring or nullLiveKit room name
agent_identitystring or nullAgent identity
caller_identitystring or nullCaller identity
latency_metricsobject or nullLatency metrics when available
analysis_dataobject or nullRedacted analysis data when available

The response model does not include recording URLs, partner tags, external references, or raw encrypted transcript artifacts.

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/call_00000000-0000-4000-a000-000000000001" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE"

Response (200 OK):

JSON
{
  "call_id": "call_00000000-0000-4000-a000-000000000001",
  "client_id": "client_example_001",
  "project_id": "proj_example_001",
  "solution_id": "sol_example_001",
  "compliance_required": true,
  "compliance_jurisdiction": "AU",
  "started_at_utc": "2026-03-15T09:30:00Z",
  "ended_at_utc": "2026-03-15T09:35:22Z",
  "duration_seconds": 322,
  "call_status": "completed",
  "call_direction": "inbound",
  "channel": "phone",
  "from_number": "+61 400 000 001",
  "to_number": "+61 2 0000 0001",
  "summary": "Caller requested an appointment booking for next Tuesday.",
  "summary_status": "ok",
  "sentiment_label_extension": null,
  "transfer_destination": null,
  "primary_outcome": {
    "id": "booked_appointment",
    "label": "Booked appointment",
    "label_key": "outcomes.booking.title",
    "state": "classified"
  },
  "sentiment_overall": "positive",
  "caller_sentiment": "positive",
  "agent_sentiment": "neutral",
  "message_count": 2,
  "transcript": [
    {"speaker": "agent", "timestamp": 0.2, "text": "Good morning, how can I help you today?", "sentiment": null},
    {"speaker": "caller", "timestamp": 3.1, "text": "I would like to book an appointment.", "sentiment": "positive"}
  ],
  "call_resolution_status": "resolved",
  "ai_first_response_seconds": 1.2,
  "human_escalation_needed": false,
  "human_intervention_count": 0,
  "cost_usd": 0.48,
  "cost_local": 0.73,
  "cost_local_currency": "AUD",
  "cost_breakdown": null,
  "llm_token_used": 1250,
  "stt_minutes_processed": 5.37,
  "tts_characters_synthesized": 3200,
  "cloud_provider": "aws",
  "cloud_region": "ap-southeast-2",
  "llm_provider": "openai",
  "llm_model": "gpt-4o",
  "stt_provider": "deepgram",
  "tts_provider": "elevenlabs",
  "tts_voice": "voice_example",
  "telephony_provider": "twilio",
  "telephony_call_id": "CA00000000000000000000000000000001",
  "livekit_room_name": "room_00000000-0000-4000-a000-000000000001",
  "agent_identity": "agent_example",
  "caller_identity": null,
  "latency_metrics": null,
  "analysis_data": null
}

#GET /v1/calls/{call_id}/inline-preview

Returns the closed, redacted preview used by Portal call-list row expansion.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
call_idpathstringYesUnique call identifier. The API looks up the call by LiveKit room SID or room name

#Response

FieldTypeDescription
call_idstringUnique call identifier
project_idstringProject identifier
client_idstringClient identifier
request_idstring or nullRequest or correlation id
started_at_utcstring (ISO 8601)Call start timestamp
ended_at_utcstring (ISO 8601) or nullCall end timestamp
call_statusstringCall status
from_numberstring or nullPII-redacted caller number
message_countinteger or nullNumber of transcript messages
caller_sentimentstring or nullCaller sentiment
agent_sentimentstring or nullAgent sentiment
transfer_destinationobject or nullRedaction-safe current transfer destination display
end_reasonstringOne of caller_hangup, transferred, failed, missed, or unknown
consent_statestringOne of obtained, not_required, or unavailable
transcript_statestringOne of ok, empty, redacted_hidden, live, redacted_failed, retention_expired, or error
transcriptarray or nullRedacted transcript preview, present only when transcript_state is ok. Preview output is capped at 200 turns
summary_statusstringBackend-owned summary status
transcript_truncatedbooleanTrue when the source transcript exceeded the preview cap

The inline preview uses the pre-redacted transcript path and does not decrypt original transcript ciphertext. Partner bearer sessions hide transcript text and return transcript_state: "redacted_hidden". Missing or out-of-scope calls return 404; routed data-host failures return 503.


#Transcript Quality

Transcript quality endpoints expose redacted transcript review state and allow eligible Portal users to record corrections, annotations, and disputes.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Endpoints

MethodPathDescription
GET/v1/calls/{call_id}/transcript-qualityReturns the transcript quality snapshot
POST/v1/calls/{call_id}/transcript-quality/correctionsCreates a transcript correction revision
POST/v1/calls/{call_id}/transcript-quality/annotationsCreates a call-level or message-level annotation
POST/v1/calls/{call_id}/transcript-quality/disputesOpens, resolves, or rejects a message dispute

#Roles

Snapshot reads are available to client_admin, client_manager, client_staff, and API-key full-access. Partner roles, internal roles, and API-key read-only receive non-enumerating 404 responses for transcript-quality reads.

Mutations require bearer Portal authentication with a portal user principal. Corrections and disputes are allowed for client_admin and client_manager. Annotations are allowed for client_admin, client_manager, and client_staff.

#Snapshot Response

FieldTypeDescription
call_idstringUnique call identifier
client_idstringClient identifier
project_idstringProject identifier
transcript_availablebooleanWhether the redacted transcript can be reviewed
unavailable_reasonstring or nullconsent_blocked, retention_blocked, deleted, or missing
can_correctbooleanWhether the caller can create corrections
can_annotatebooleanWhether the caller can create annotations
can_resolve_disputesbooleanWhether the caller can resolve or reject disputes
messagesarrayTranscript messages with display text, correction history, annotations, and dispute state
call_annotationsarrayCall-level annotations
analytics_source_statusstringoriginal_transcript or reanalysis_unavailable
quality_summaryobjectCorrection, annotation, open-dispute, and last-activity summary

#Mutation Bodies

EndpointBody
Corrections{ "message_index": 0, "corrected_text": "...", "reason": "..." }
Annotations{ "scope": "message", "message_index": 0, "content": "..." } or { "scope": "call", "content": "..." }
Disputes{ "message_index": 0, "status": "open" }, { "message_index": 0, "status": "resolved", "note": "..." }, or { "message_index": 0, "status": "rejected", "note": "..." }

User-entered quality text is normalized before persistence. Phone numbers, email addresses, card-like numbers, and identifier-like values are replaced with safe placeholders; secret-like content is rejected. Rejected or unchanged quality text returns 422 QUALITY_TEXT_REJECTED. Invalid request bodies return 400; denied mutations return 403 for read-entitled callers without mutation rights and non-enumerating 404 for callers without transcript-content access; missing or out-of-scope calls return 404; workflow failures return 503.


#GET /v1/calls/{call_id}/transcript-download

Downloads the redacted transcript for a call.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>) Scopes: client_admin, client_manager, client_staff, or partner_admin

Data handling: Transcripts may contain personal information subject to the Privacy Act 1988 (Cth). All transcript access is audit-logged. Partners should refer to their AiDial partner agreement for data handling obligations. Transcripts are redacted by default based on your client PII configuration. Secondary-use policy does not gate this direct redacted transcript access path; it continues to apply to reuse/export, webhook delivery, reporting, and model-training decisions.

#Parameters

NameInTypeRequiredDefaultDescription
call_idpathstringYesUnique call identifier
formatquerystringNotxtDownload format. Current implementation supports txt only
include_quality_historyquerybooleanNofalseInclude separately labelled correction, annotation, and dispute history in the TXT output

#Response

Returns the transcript as a streamed text attachment (Content-Type: text/plain; charset=utf-8). The attachment filename uses AiDial-Transcript_<project-display-or-id>_<YYYY-MM-DD>_<call-id-suffix>.txt, where <call-id-suffix> is the last 12 characters of the sanitised call id and the date segment is evaluated in the project timezone when available. The text starts with readable call metadata headers (Business, Call date, Duration, Direction, From, To) followed by role-prefixed transcript lines. Project display name and timezone are loaded through tenant-scoped project metadata when they are not present on the routed call row; unavailable metadata falls back to project id and UTC. When include_quality_history=true, correction, annotation, and dispute history is appended in separately labelled sections. Unsupported formats return 400 EXPORT_UNSUPPORTED_FORMAT. If the call is out of scope, unavailable, or the caller lacks transcript access, the endpoint returns 404 Not Found. Download throttling returns 429 with code DOWNLOAD_THROTTLED; generation failures return 503 DOWNLOAD_FAILED.

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/call_00000000-0000-4000-a000-000000000001/transcript-download" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE" \
  -o transcript.txt

#GET /v1/calls/{call_id}/transcript/pdf

Downloads the redacted transcript for a call as a PDF attachment.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>) Scopes: client_admin, client_manager, client_staff, or partner_admin

Data handling: The PDF is generated from the same redacted transcript path as TXT download. Transcript access is audit-logged. Secondary-use policy does not gate this direct redacted transcript access path; it continues to apply to reuse/export, webhook delivery, reporting, and model-training decisions.

#Parameters

NameInTypeRequiredDefaultDescription
call_idpathstringYesUnique call identifier

#Response

Returns the transcript as a streamed PDF attachment (Content-Type: application/pdf). The attachment filename uses AiDial-Transcript_<project-display-or-id>_<YYYY-MM-DD>_<call-id-suffix>.pdf, where <call-id-suffix> is the last 12 characters of the sanitised call id and the date segment is evaluated in the project timezone when available, and the PDF header uses the same tenant-scoped project display name, timezone, and redacted call metadata as the TXT export. Per-message labels use elapsed MM:SS offsets when offset or timestamp evidence is available. Transcripts over the 500 KB raw transcript size guard return 413 with code DOWNLOAD_PDF_TOO_LARGE; use TXT download for oversized transcripts. Download throttling returns 429 with code DOWNLOAD_THROTTLED.

#Example

Request:

Bash
curl -X GET "https://api.aidial.ai/v1/calls/call_00000000-0000-4000-a000-000000000001/transcript/pdf" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE" \
  -o transcript.pdf

#POST /v1/calls/export

Exports filtered call data as a CSV file.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>) Scopes (metadata export — sub-surface S-006-METADATA): client_admin, client_manager, client_staff, or partner_admin Scopes (sensitive opt-ins — sub-surfaces S-006-CALLER_INFO, S-006-TRANSCRIPT, S-006-AUDIO_LINKS): client_admin only (S19). Audio-link opt-in requests additionally require project audio retention enabled, but the current CSV writer does not emit a signed audio-link column.

Export behaviour: When rows match, this endpoint returns a streamed CSV file directly in the response body (Content-Type: text/csv; charset=utf-8). It is not an asynchronous job. Large date ranges may result in longer response times. Set appropriate client-side timeouts (recommended minimum: 300 seconds for full-range exports).

#Request Body

FieldTypeRequiredDescription
project_idstringYesProject to export
start_datestringNoStart date (inclusive). YYYY-MM-DD or ISO 8601
end_datestringNoEnd date (inclusive). YYYY-MM-DD or ISO 8601
statusstringNoFilter by call status
callerstringNoFilter by caller
agentstringNoFilter by agent
displayed_matched_countintegerNoAdvisory browser-side matched count for audit context (0-100000)
formatstringNoExport format. Currently only csv
include_caller_infobooleanYes (S19)Opt-in for raw caller fields. Honoured only for client_admin.
include_transcript_textbooleanYes (S19)Opt-in for decrypted transcript text. Honoured only for client_admin. Triggers server-side transcript decrypt.
include_audio_linksbooleanYes (S19)Opt-in for signed audio links. The request is role- and retention-gated, but this release does not include signed audio links in the CSV or in options_honoured. Raw object-storage paths and keys are never returned.

#Response

When rows match, returns a CSV attachment with these columns:

ColumnDescription
call_idUnique call identifier
project_idProject identifier
call_started_atCall start timestamp
call_statusCall status
channelCommunication channel
callerCaller number, redacted based on role and PII configuration
agentAgent identity
duration_secondsCall duration
summaryCall summary
transcriptCollapsed redacted transcript text

CSV values that could be interpreted as spreadsheet formulas are neutralised before streaming.

Sensitive export audit metadata records selected options separately from honoured options. include_audio_links=true is retained in selected options after it passes role and retention checks, but audio_links is deliberately excluded from honoured options until signed-link generation is implemented.

If no rows match, the endpoint returns 200 OK with JSON:

JSON
{
  "code": "EXPORT_EMPTY",
  "message": "No matching calls found.",
  "messageKey": "calls.export.messages.empty"
}

Synchronous exports are capped at 10,000 matching rows and guarded by a 30 second wall-time limit. Row counts over the hard cap return 413 EXPORT_TOO_LARGE before the cursor opens. If the wall-time guard trips during streaming, the export stops and the audit row records EXPORT_TOO_LARGE with failure_reason: "wall_time_exceeded". The legacy EXPORT_OVER_CAP (400) code remains for the browser-claimed advisory count cap.

S19 error semantics:

  • 422 — one or more required typed booleans are missing or are not JSON booleans. This validation happens before router role, capability, count, or audit handling.
  • 400 EXPORT_PROJECT_REQUIREDproject_id was omitted or blank.
  • 400 EXPORT_UNSUPPORTED_FORMAT — unsupported export format, when the request reaches the router. The current request model accepts only csv, so most non-CSV values fail request validation before this branch.
  • 404 EXPORT_DENIED — the caller's base role cannot use the export surface, or the selected project is out of scope.
  • 403 EXPORT_DENIED — sensitive opt-in requested by a non-client_admin role; the request is rejected before any count, cursor, decrypt, or signed-link generation runs.
  • 400 EXPORT_UNAVAILABLEinclude_audio_links=true against a project whose audio retention is disabled or cannot be confirmed.
  • 413 EXPORT_TOO_LARGE — synchronous row cap exceeded before streaming starts. Wall-time overruns are recorded in export audit metadata while streaming is stopped.

S19 example body (metadata-only):

JSON
{
  "project_id": "proj_example_001",
  "start_date": "2026-03-01",
  "end_date": "2026-03-15",
  "format": "csv",
  "include_caller_info": false,
  "include_transcript_text": false,
  "include_audio_links": false
}

#Example

Request:

Bash
curl -X POST "https://api.aidial.ai/v1/calls/export" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "proj_example_001",
    "start_date": "2026-03-01",
    "end_date": "2026-03-15",
    "format": "csv",
    "include_caller_info": false,
    "include_transcript_text": false,
    "include_audio_links": false
  }' \
  -o calls_export.csv

#Rate Limiting

Only one export can run at a time for the same user and tenant. Concurrent exports return 409 with code EXPORT_IN_PROGRESS. If rate-limited, the export endpoint returns:

Response (429 Too Many Requests):

JSON
{
  "error": "rate_limited",
  "code": "EXPORT_THROTTLED",
  "message": "Export throttled. Please wait and try again.",
  "messageKey": "calls.export.messages.throttled",
  "retry_after_seconds": 60
}

#POST /v1/calls/{call_id}/audit-pii-access

Logs that PII for a call was accessed. This endpoint records the access against the routed call record and emits a portal audit event.

Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)

#Parameters

NameInTypeRequiredDefaultDescription
call_idpathstringYesUnique call identifier

#Request Body

FieldTypeRequiredDescription
access_typestringYesType of access, such as view_transcript, export_transcript, or view_phone
accessed_bystringYesUser identifier, such as a portal user ID or email
access_contextstringNoAccess context. Defaults to api

#Response

FieldTypeDescription
statusstringsuccess when the audit event is recorded
messagestringHuman-readable status message

#Example

Request:

Bash
curl -X POST "https://api.aidial.ai/v1/calls/call_00000000-0000-4000-a000-000000000001/audit-pii-access" \
  -H "X-API-Key: sk_live_YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"access_type": "view_transcript", "accessed_by": "user@example.com", "access_context": "portal"}'

Response (200 OK):

JSON
{
  "status": "success",
  "message": "PII access logged successfully"
}

#Data Scoping

All responses are scoped to your authenticated client_id and data_env. You only see calls belonging to your organisation and routed environment. Accessing calls outside your scope returns 404 Not Found.