Skip to main content

CQA API Reference

Overview​

Exotel CQA (Conversation Quality Analysis) provides AI-powered quality analysis for contact center interactions. The platform ingests interaction data -- audio recordings, transcripts, and metadata -- from any source, runs them through configurable quality profiles, and produces detailed quality scores.

This document covers the public API surface:

  • Data Import API -- Push interactions into CQA via REST (single, batch, or file-based).
  • Analysis API -- Retrieve detailed quality analysis results.
  • File Schemas -- CSV format specifications for bulk ingestion.

Base URL​

All endpoints are served under the CQA context path:

https://{host}/cqa

Replace {host} with the hostname of your CQA deployment (e.g. cqa.exotel.com).


Authentication​

All Data Import and Analysis API endpoints authenticate via an API key passed in the X-API-Key header.

API SurfaceAuth MethodHeader
Data Import APIAPI KeyX-API-Key: {key}
Analysis APIAPI KeyX-API-Key: {key}

API Keys​

API keys are scoped to a single account and are used for all programmatic ingestion and analysis retrieval. API keys can be created and managed through the CQA dashboard.


Rate Limits​

API endpoints are rate-limited per tenant (account).

  • Requests that exceed the limit receive 429 Too Many Requests with error code **RATE_LIMIT_EXCEEDED** (see Response Envelope).
  • Too many concurrent file jobs is a separate 429 with code **TOO_MANY_JOBS**.
Endpoint PatternMethodDefault tenant limit (typical)
/ingress/interactions* (ingest)POST100 requests per minute
/ingress/** (tracking)GET300 requests per minute

Response Envelope​

All Data Import and Analysis API endpoints return responses in a common envelope.

Success response:

{
"status": 200,
"request_id": "d4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8",
"data": { }
}

Error response:

{
"status": 400,
"request_id": "d4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8",
"message": "Descriptive error message",
"error": {
"code": "VALIDATION_ERROR"
}
}
FieldTypeDescription
statusintegerHTTP status code mirrored in the body.
request_idstringUnique request identifier for tracing and support.
messagestringHuman-readable detail (present on many errors; may be omitted when redundant).
dataobjectResponse payload (present on success).
errorobjectPresent on failure. Contains **code** only.

Error Codes:

CodeHTTP StatusDescription
VALIDATION_ERROR400Request failed validation (missing required fields, exceeded limits).
INVALID_JSON400Request body is not valid JSON.
INVALID_REQUEST400Request contains invalid arguments.
UNAUTHORIZED401Missing or invalid API key.
NOT_FOUND404The requested resource was not found.
DUPLICATE409Returned for single-ingest conflict responses (see Ingest a Single Interaction).
RATE_LIMIT_EXCEEDED429Tenant or user rate limit exceeded.
TOO_MANY_JOBS429Too many concurrent file ingestion jobs for this account.
INTERNAL_ERROR500An unexpected server error occurred.

Data Import API

The Data Import API is the primary external integration point for pushing interaction data into CQA. It supports three ingestion modes: single, batch, and file-based.

Base path: /api/v1/accounts/{account_id}/ingress Auth: X-API-Key header


Ingest a Single Interaction​

Submits one interaction for quality analysis. Returns immediately with a tracking ID.

POST

https://{host}/cqa/api/v1/accounts/{account_id}/ingress/interactions

Request Parameters (JSON Body)​

Content requirement: At least one of audio_url, transcript_url must be provided.

Parameter NameMandatory / OptionalTypeDescription
external_interaction_idMandatorystringYour unique identifier for this interaction. Used for deduplication.
channel_typeMandatorystringInteraction channel. Recognized values: VOICE, CHAT, EMAIL, SMS, WHATSAPP. Other values are accepted (not rejected).
sourceOptionalstringIdentifies the originating system (e.g. my-pbx, genesys).
languageOptionalstringLanguage code (e.g. en, hi, es).
interaction_start_timeOptionalstring (ISO-8601)When the interaction started (e.g. 2026-04-01T10:30:00Z).
duration_secondsOptionalintegerDuration of the interaction in seconds.
audio_formatOptionalstringAudio format hint (e.g. WAV, MP3, OGG).
callback_urlOptionalstringWebhook URL for status update notifications.
audio_urlMandatory if transcript_url is not providedstringDirect URL to the audio recording.
transcript_urlMandatory if audio_url is not providedstringDirect URL to the transcript file.
pii_redactedOptionalbooleanWhether PII has already been redacted in the provided content. Default: false.
metadataOptionalobjectArbitrary key-value pairs for tagging. Maximum 50 keys. Values can be strings, numbers, or booleans.

Example Request​

curl -X POST "https://{host}/cqa/api/v1/accounts/{account_id}/ingress/interactions" \
-H "X-API-Key: {your_api_key}" \
-H "Content-Type: application/json" \
-d '{
"external_interaction_id": "call-2026-04-01-001",
"channel_type": "VOICE",
"source": "my-pbx",
"language": "en",
"interaction_start_time": "2026-04-01T10:30:00Z",
"duration_seconds": 300,
"audio_format": "WAV",
"audio_url": "https://storage.example.com/recordings/call-001.wav",
"pii_redacted": false,
"callback_url": "https://my-app.example.com/webhooks/cqa",
"metadata": {
"agent_id": "agent-42",
"campaign": "retention-q2",
"disposition": "RESOLVED"
}
}'

Response​

**201 Created** -- Interaction queued successfully.

{
"status": 201,
"request_id": "req-abc-123",
"message": "Queued for processing",
"data": {
"interaction_id": "550e8400-e29b-41d4-a716-446655440000",
"external_interaction_id": "call-2026-04-01-001",
"status": "queued"
}
}

**409 Conflict** -- Duplicate external_interaction_id.

{
"status": 409,
"request_id": "req-abc-124",
"message": "Duplicate: interaction with this external_interaction_id already exists",
"error": {
"code": "DUPLICATE"
}
}

**400 Bad Request** -- Validation error.

{
"status": 400,
"request_id": "req-abc-125",
"message": "metadata must not exceed 50 keys",
"error": {
"code": "VALIDATION_ERROR"
}
}

Response Fields​

Parameter NameTypeDescription
interaction_idstring (UUID)CQA-assigned unique identifier for the interaction.
external_interaction_idstringYour identifier, echoed back.
statusstringqueued on success.
messagestringTop-level hint on 201 (e.g. queued). On errors, the detail text is in message, not inside error.

Ingest a Batch of Interactions​

Submits up to 100 interactions as a single asynchronous job. Returns an id in data for tracking.

POST

https://{host}/cqa/api/v1/accounts/{account_id}/ingress/interactions/batch

Request Parameters (JSON Body)​

Parameter NameMandatory / OptionalTypeDescription
interactionsMandatoryarrayList of interaction objects, each following the same schema as the single ingest endpoint. Minimum 1, maximum 100.
skip_duplication_checkOptionalbooleanIf true, skip deduplication by external_interaction_id. Default: false.

Response Fields​

Parameter NameTypeDescription
idstringUnique identifier for the batch job. Use this with the batch tracking endpoint.
typestringAlways batch for this endpoint.
statusstringpending -- the job has been accepted and is queued for processing.

Submit a File for Ingestion​

Submits a remote CSV file URL for asynchronous ingestion. CQA downloads and processes the file in the background.

POST

https://{host}/cqa/api/v1/accounts/{account_id}/ingress/interactions/files

Request Parameters (JSON Body)​

Parameter NameMandatory / OptionalTypeDescription
file_urlMandatorystringURL to the file. Supported schemes: https://, http://, s3://. https is strongly recommended; http is accepted but offers no transport encryption. Private/local addresses (localhost, 127.0.0.1, 10.x, 192.168.x, 172.16.x) are rejected.
formatMandatorystringFile format: csv or ndjson.
sourceOptionalstringDefault source applied to all rows where the row-level source is not set.
pii_redactedOptionalbooleanDefault PII flag applied to all rows.
callback_urlOptionalstringDefault callback URL stored per row (same semantics as single ingest; no HTTP callback from ingress).
column_mappingOptionalobjectMaps your CSV headers to canonical column names. Keys are your original headers (trimmed, lowercased); values are canonical names. Ignored for NDJSON. See CSV Schema for canonical names.
metadataOptionalobjectDefault metadata merged into every row. After merge, each row should respect the 50-key metadata limit enforced for batch/single ingest; avoid large default maps that push merged rows over the limit.
skip_duplication_checkOptionalbooleanIf true, skip deduplication. Default: false.

File Processing Limits​

LimitDefault Value
Max rows per file job100,000
Max file size100 MB

Get Interaction by ID​

Retrieves the current status and details of an ingested interaction.

GET

https://{host}/cqa/api/v1/accounts/{account_id}/ingress/interactions/{interaction_identifier}

Path Parameters​

Parameter NameMandatory / OptionalDescription
account_idMandatoryYour CQA account ID.
interaction_identifierMandatoryEither the CQA-assigned UUID (interaction_id) or your external_interaction_id.

Response Fields​

Parameter NameTypeDescription
interaction_idstring (UUID)CQA-assigned unique identifier.
external_interaction_idstringYour identifier.
batch_idstringJob/batch ID if the interaction was part of a batch or file job. Omitted for single ingestion.
channel_typestringChannel type as submitted.
sourcestringOriginating system.
statusstringCurrent status: queued, processing, completed, or failed.
status_modified_atstring (ISO-8601)When the status last changed.
failure_reasonstringReason for failure. Only present when status is failed; omitted otherwise.
audio_urlstringResolved audio recording URL (same value as submitted audio_url / files audio URL).
transcript_urlstringResolved transcript URL.
pii_redactedbooleanWhether PII was flagged as redacted.
created_atstring (ISO-8601)When the interaction was ingested.
metadataobjectKey-value metadata.
analysesarrayList of analyses triggered for this interaction. Each contains analysis_id, profile_id, and status.

Interaction Status Lifecycle​

Applies to the status field on individual interactions and analyses.

API StatusMeaning
queuedInteraction accepted, waiting to be processed.
processingAnalysis is underway.
completedAll analyses finished successfully.
failedProcessing failed (check failure_reason).

Job Status Lifecycle​

Applies to the status field in the batch/file 202 response and the job_status field in the batch tracking response. Job statuses are distinct from interaction statuses.

Job StatusMeaning
pendingJob accepted and queued. Returned in the initial 202 response.
processingA worker has picked up the job and is processing rows.
completedAll rows have been processed (check accepted/rejected for counts).
failedThe job failed entirely (check error_message).

Track Batch / File Job​

Retrieves all interactions for a batch or file job, with pagination and job-level status.

GET

https://{host}/cqa/api/v1/accounts/{account_id}/ingress/interactions/batch/{id}

Path Parameters​

Parameter NameMandatory / OptionalDescription
account_idMandatoryYour CQA account ID.
idMandatoryThe job identifier: the **id** returned in the batch or file 202 response (data.id).

Query Parameters​

Parameter NameDefaultMaxDescription
page0--Zero-based page index.
size20100Number of interactions per page. Values above 100 are silently clamped to 100.

Response Fields​

Parameter NameTypeDescription
idstringThe job identifier (same as data.id from the batch or file 202 response).
totalintegerTotal interactions associated with this job.
interactionsarrayPaginated list of interaction detail objects.
paginationobjectContains page, size, total_elements, total_pages.
job_statusstringOverall job status: pending, processing, completed, or failed.
job_typestringbatch or file.
total_rowsintegerTotal rows found in the input (includes accepted + rejected).
acceptedintegerNumber of rows successfully processed.
rejectedintegerNumber of rows that failed validation.
errorsarrayUp to 100 error entries. Each has line (row number), reason, and external_interaction_id.
error_messagestringTop-level error message if the entire job failed.
completed_atstring (ISO-8601)When the job finished processing.

Analysis API

Retrieve detailed quality analysis results for a completed analysis.

Base path: /api/v1/accounts/{account_id}/analyses Auth: X-API-Key header


Get Analysis Detail​

Returns the full scoring breakdown for a specific analysis, including categories, subcategories, and individual KPI scores.

GET

https://{host}/cqa/api/v1/accounts/{account_id}/analyses/{analysis_id}

Path Parameters​

Parameter NameMandatory / OptionalDescription
account_idMandatoryYour CQA account ID.
analysis_idMandatoryThe analysis UUID (obtained from the interaction detail's analyses array).

Response Fields​

Parameter NameTypeDescription
analysis_idstring (UUID)Unique analysis identifier.
interaction_idstring (UUID)The interaction this analysis belongs to.
external_interaction_idstringYour interaction identifier.
profile_idstringQuality profile used for scoring.
profile_namestringHuman-readable quality profile name.
statusstringqueued, processing, completed, or failed.
ai_scorefloatAI-generated quality score.
qa_scorefloatManual QA score (if a human reviewer overrode). Omitted if no manual review has occurred.
final_scorefloatEffective score (QA score if present, otherwise AI score).
criticality_adjusted_scorefloatScore after applying criticality weights.
max_scorefloatMaximum possible score for this profile.
analysis_completed_atstring (ISO-8601)Timestamp derived from the interaction’s last status change in the account timezone (not a separate analysis-completion clock). May not equal a pure β€œanalysis finished” instant in all edge cases.
failure_reasonstringNot populated in the current response (null omitted). Use status and support channels when an analysis fails.
categoriesarrayScored categories. See Category object below.
metadataobjectInteraction metadata, echoed for convenience.

Category Object​

FieldTypeDescription
namestringCategory name (e.g. "Communication Skills").
ai_scorefloatAI score for the category.
qa_scorefloatManual QA score for the category. Omitted if not set.
final_scorefloatFinal score for the category.
criticality_adjusted_scorefloatCriticality-adjusted score.
max_scorefloatMaximum possible score.
sub_categoriesarraySubcategories within this category.

SubCategory Object​

FieldTypeDescription
namestringSubcategory name.
qa_scorefloatManual QA score for the subcategory. Omitted if not set.
kpisarrayIndividual KPIs scored within this subcategory.

KPI Object​

FieldTypeDescription
kpi_namestringKPI name (e.g. "Proper Greeting").
ai_responsestringThe AI's answer (e.g. "Yes", "No", "Partially").
ai_justificationstringThe AI's reasoning for its score.
ai_suggestionstringOptional AI suggestion text. Omitted if not set.
ai_scorefloatAI score for this KPI.
qa_scorefloatManual QA score for this KPI. Omitted if not set.
qa_justificationstringQA justification text. Omitted if not set.
qa_selected_kpi_option_uidstringUID of the KPI option selected by QA, if any.
qa_selected_kpi_option_valuestringLabel/value of the QA-selected KPI option.
user_commentstringFree-text QA/user comment on this KPI.
final_scorefloatFinal score (may reflect QA override).
criticality_adjusted_scorefloatCriticality-adjusted score.
max_scorefloatMaximum possible score for this KPI.

File Schemas

When using the file-based ingestion endpoint (POST /interactions/files), CQA supports two file formats: CSV and NDJSON.


CSV Schema​

The first row of a CSV file must contain column headers. Headers are trimmed and lowercased before matching against canonical names.

Canonical Column Names​

ColumnRequiredTypeDescription
external_interaction_idYesstringYour unique interaction identifier.
channel_typeYesstringVOICE, CHAT, EMAIL, SMS, WHATSAPP.
sourceNostringOriginating system identifier.
languageNostringLanguage code (e.g. en).
interaction_start_timeNoISO-8601 stringISO-8601 UTC format (e.g. 2026-04-01T10:00:00Z).
duration_secondsNointegerInteraction duration in seconds.
audio_formatNostringFormat hint (e.g. WAV, MP3).
callback_urlNostringPer-row callback URL (stored; no HTTP callback from ingress).
pii_redactedNobooleantrue or false.
audio_urlYes (Mandatory if transcript_url is not provided)stringAudio file URL(s). Supports multiple URLs separated by ;.
transcript_urlYes (Mandatory if audio_url is not provided)stringTranscript file URL(s). Supports multiple URLs separated by ;.
file_urlNostringGeneric file URL. Used with file_type as a fallback when no audio_url/transcript_url entries exist.
file_typeNostringFile extension for type resolution. Audio extensions: mp3, wav, ogg, flac, m4a, aac, wma, amr. Transcript extensions: txt, pdf, doc, docx, srt, vtt.

Content requirement: Each row must have at least one of audio_url, transcript_url.

Extra Columns Become Metadata​

Any column header that is not in the canonical set above is automatically added to the row's metadata map. For example, columns named agent, campaign, or disposition become metadata key-value pairs without any extra configuration.

Column Mapping​

If your CSV uses non-standard headers, supply a column_mapping object in the file submission request to rename them. Keys are your original headers (trimmed, lowercased); values are canonical names.

Example -- given a CSV with headers call_id,type,recording,agent,campaign:

{
"column_mapping": {
"call_id": "external_interaction_id",
"type": "channel_type",
"recording": "audio_url"
}
}

After mapping, agent and campaign are not canonical, so they automatically become metadata.

Example CSV​

external_interaction_id,channel_type,audio_url,transcript_url,language,agent,campaign
call-001,VOICE,https://s3.example.com/rec-001.wav,https://s3.example.com/tr-001.txt,en,agent-42,retention
call-002,VOICE,https://s3.example.com/rec-002.wav,,hi,agent-15,support
call-003,CHAT,,,,agent-42,retention

Note: Row 3 (call-003) would fail validation because it has no content source (no audio, transcript).


Request-Level Defaults for File Ingestion​

For both CSV and NDJSON file submissions, fields set on the POST /interactions/files request body are applied as defaults to every row:

Request FieldBehavior
sourceApplied to rows where the row-level source is null or empty.
pii_redactedApplied to rows where the row-level value is null.
callback_urlApplied to rows where the row-level value is null.
metadataMerged with each row's metadata. Row-level keys take precedence.

Limits and Constraints

ConstraintValue
Max interactions per batch100
Max metadata keys per interaction50 (enforced on single/batch request bodies; keep merged file-row metadata within this bound)
Max rows per file job100,000
Max file size per file job100 MB
Max concurrent file jobs per account5 (default)
Batch tracking page size (max)100 (silently clamped)
Supported file formatscsv, ndjson
Supported file URL schemeshttps, http, s3 (https recommended)
Supported channel typesVOICE, CHAT, EMAIL, SMS, WHATSAPP