---
title: 8. API Reference Guide
slug: conversation-intelligence/8-api-reference-guide
docTags: 
createdAt: 2026-10-21T11:17:48.416Z
---

**Target audience:** Developers, IT partners, integration teams

**Description:** The public API contract for pushing interactions into CQA and pulling analysis results out: authentication, endpoints, file schemas, callbacks, and limits. To get an API key, see [5.4 Access API](https://docs.exotel.com/conversation-intelligence/5-manage-data-integration#54-access-api). For an end-to-end setup and verification walkthrough, see the [Standard API playbook](https://docs.exotel.com/conversation-intelligence/5-manage-data-integration#standard-api).

***

# 8.1. Overview

The CQA API has four parts:

| Part                     | What it does                                                          | Section                                                                                              |
| ------------------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Data Import API**      | Push interactions into CQA — one at a time, in a batch, or as a file. | [6.4](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#64-data-import-api)    |
| **Analysis API**         | Retrieve quality analysis results.                                    | [6.5](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#65-analysis-api)       |
| **File schemas**         | CSV and NDJSON formats for file-based ingestion.                      | [6.6](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#66-file-schemas)       |
| **Webhooks / callbacks** | Notifications CQA sends to your system as interactions are processed. | [6.7](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#67-webhooks-callbacks) |

:::BlockQuote
**"Job" in this chapter** means an API ingestion job — the asynchronous work created by a batch or file submission. It is not the same as a job in **Manage › Data Integration › Job & Schedule**, which moves data through a connector on a schedule.
:::

***

# 8.2. Authentication

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

| API             | Auth method | Header             |
| --------------- | ----------- | ------------------ |
| Data Import API | API key     | `X-API-Key: {key}` |
| Analysis API    | API key     | `X-API-Key: {key}` |

**Getting a key.** An Admin generates keys in **Manage › Data Integration › Access API** (page title **API Keys**). The key card shows the **API Key** and **Account ID** you need for every request. Follow the checklist in [5.4 Access API](https://docs.exotel.com/conversation-intelligence/5-manage-data-integration#54-access-api).

- Keys are scoped to one account and are used for all programmatic ingestion and analysis retrieval.
- An account can have at most **2** active keys.
- A revoked key disappears from the list immediately, but the API may continue to accept it for up to **24 hours**. Put the replacement key in place before you revoke the old one.
- Store keys in a secrets manager. Never put them in tickets, chat, or documents.

***

# 8.3. Base URL and conventions

## Base URL

All endpoints are served under the CQA context path `/cqa/api/v1`:

```text
https://{host}/cqa/api/v1/accounts/{account_id}/...
```

- `{host}` is your CQA console host — for example, `cqa-console.in.exotel.com`.
- `{account_id}` is the Account ID shown on your API key card.

## Response envelope

All Data Import and Analysis API endpoints return the same envelope.

**Success:**

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

**Error:**

```json
{
  "status": 400,
  "request_id": "d4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8",
  "message": "Descriptive error message",
  "error": {
    "code": "VALIDATION_ERROR"
  }
}
```

| Field        | Type    | Description                                                                   |
| ------------ | ------- | ----------------------------------------------------------------------------- |
| `status`     | integer | HTTP status code, mirrored in the body.                                       |
| `request_id` | string  | Unique request identifier. Include it when you contact support.               |
| `message`    | string  | Human-readable detail. Present on most errors; may be omitted when redundant. |
| `data`       | object  | Response payload (on success).                                                |
| `error`      | object  | Present on failure. Contains `code` only.                                     |

## Error codes

| Code                        | HTTP status | Description                                                                                                                                                                                                                            |
| --------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`          | 400         | Request failed validation (missing required fields, exceeded limits).                                                                                                                                                                  |
| `INVALID_JSON`              | 400         | Request body is not valid JSON.                                                                                                                                                                                                        |
| `INVALID_REQUEST`           | 400         | Request contains invalid arguments.                                                                                                                                                                                                    |
| `UNAUTHORIZED`              | 401         | Missing or invalid API key.                                                                                                                                                                                                            |
| `NOT_FOUND`                 | 404         | The requested resource was not found.                                                                                                                                                                                                  |
| `DUPLICATE`                 | 409         | An interaction with this `external_interaction_id` already exists (single ingest).                                                                                                                                                     |
| `RATE_LIMIT_EXCEEDED`       | 429         | Tenant or user rate limit exceeded. See [6.8 Limits](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#68-limits).                                                                                               |
| `TOO_MANY_JOBS`             | 429         | Too many concurrent file ingestion jobs for this account.                                                                                                                                                                              |
| `CONVERSATION_CAP_EXCEEDED` | 429         | The account's daily, weekly, or monthly conversation cap is reached. Admins can see caps and usage in footer **Usage & Limit** ([7.1](https://docs.exotel.com/conversation-intelligence/7-usage-policies-and-support#71-usage-limit)). |
| `INTERNAL_ERROR`            | 500         | An unexpected server error occurred.                                                                                                                                                                                                   |

***

# 8.4. Data Import API

The Data Import API is the main way to push interaction data into CQA from external systems. It supports three modes: single, batch, and file.

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

## Content and URL requirements

- Every interaction needs at least one of `audio_url`, `transcript_url`, or `transcript_text`.
- `audio_url` and `transcript_url` must be publicly accessible or pre-signed, so CQA can fetch them.
- If both `transcript_url` and `transcript_text` are sent, `transcript_url` is used and the inline text is ignored.

## Ingest a single interaction

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

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

**Request body (JSON)**

| Parameter                 | Required                         | Type              | Description                                                                                                                                                                                                                                                       |
| ------------------------- | -------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_interaction_id` | Yes                              | string            | Your unique identifier for the interaction. Used for deduplication: an interaction with an ID that already exists is rejected.                                                                                                                                    |
| `channel_type`            | Yes                              | string            | Interaction channel. Recognized values: `VOICE`, `CHAT`, `EMAIL`, `SMS`, `WHATSAPP`. Other values are accepted.                                                                                                                                                   |
| `source`                  | No                               | string            | Originating system (for example, `my-pbx`, `genesys`).                                                                                                                                                                                                            |
| `language`                | No                               | string            | Language code (for example, `en`, `hi`, `es`).                                                                                                                                                                                                                    |
| `interaction_start_time`  | No                               | string (ISO-8601) | When the interaction started (for example, `2026-04-01T10:30:00Z`).                                                                                                                                                                                               |
| `duration_seconds`        | No                               | integer           | Duration of the interaction in seconds.                                                                                                                                                                                                                           |
| `audio_format`            | No                               | string            | Audio format hint (for example, `WAV`, `MP3`, `OGG`).                                                                                                                                                                                                             |
| `callback_url`            | No                               | string            | URL for status notifications. See [6.7 Webhooks / callbacks](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#67-webhooks-callbacks).                                                                                                      |
| `audio_url`               | If no transcript                 | string            | Direct URL to the audio recording.                                                                                                                                                                                                                                |
| `transcript_url`          | If no audio or `transcript_text` | string            | Direct URL to the transcript file.                                                                                                                                                                                                                                |
| `transcript_text`         | If no audio or `transcript_url`  | string            | Inline transcript text. Max 100 KB (UTF-8).                                                                                                                                                                                                                       |
| `pii_redacted`            | No                               | boolean           | Default `false`. Set to `true` if PII is already redacted in the content you send — by your own process or by CQA's [PII redaction](https://docs.exotel.com/conversation-intelligence/4-manage-configuration#47-pii-redaction) — so CQA does not redact it again. |
| `metadata`                | No                               | object            | Key-value pairs for tagging. Max 50 keys. Values can be strings, numbers, or booleans. These become the interaction's [metadata](https://docs.exotel.com/conversation-intelligence/5-manage-data-integration#52-metadata).                                        |

**Example — voice call with audio URL**

```bash
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"
    }
  }'
```

**Example — chat with transcript URL**

```bash
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": "chat-2026-04-01-001",
    "channel_type": "CHAT",
    "source": "whatsapp",
    "language": "en",
    "interaction_start_time": "2026-04-01T10:30:00Z",
    "transcript_url": "https://storage.example.com/messages/chat-001.txt"
  }'
```

**Example — chat with inline transcript**

```bash
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": "chat-2026-09-03-001",
    "channel_type": "CHAT",
    "source": "web-chat",
    "language": "en",
    "transcript_text": "Agent: Hello! How can I help you today?\nCustomer: I need to reset my password.\nAgent: Sure, I can help with that. Let me send you a reset link."
  }'
```

**Responses**

`201 Created` — interaction queued:

```json
{
  "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`:

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

`429 Too Many Requests` — conversation cap reached:

```json
{
  "status": 429,
  "request_id": "req-abc-126",
  "message": "Daily conversation cap exceeded: 50/50",
  "error": { "code": "CONVERSATION_CAP_EXCEEDED" }
}
```

`400 Bad Request` — validation error:

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

**Response fields (`data`)**

| Field                     | Type          | Description                                                                                                                     |
| ------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `interaction_id`          | string (UUID) | CQA-assigned unique identifier.                                                                                                 |
| `external_interaction_id` | string        | Your identifier, echoed back.                                                                                                   |
| `status`                  | string        | `queued` on success.                                                                                                            |
| `message`                 | string        | Top-level hint on `201` (for example, "Queued for processing"). On errors, the detail text is in `message`, not inside `error`. |

On errors, the detail text is in the top-level `message`, not inside `error`.

## Ingest a batch of interactions

Submits 1 to 100 interactions as a single asynchronous job and returns a job `id` for tracking.

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

**Request body (JSON)**

| Parameter      | Required | Type  | Description                                                                                                                                                                   |
| -------------- | -------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `interactions` | Yes      | array | 1–100 interaction objects, each with the same fields as [single ingest](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#ingest-a-single-interaction). |

Each item's `transcript_text` can be at most 100 KB (102,400 bytes). If any item exceeds this, the whole request is rejected with `400 VALIDATION_ERROR` and no job is created.

**Response fields (`data`)**

| Field    | Type   | Description                                                                                                                                                 |
| -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`     | string | Job identifier. Use it with [Track a batch or file job](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#track-a-batch-or-file-job). |
| `type`   | string | Always `batch`.                                                                                                                                             |
| `status` | string | `pending` — the job is accepted and queued.                                                                                                                 |

**Partial acceptance.** If some items fail request-time validation and others are valid, the API returns `202 Accepted`. Valid items are queued; invalid items are rejected immediately. The response can include `message`, `accepted`, `rejected`, and `errors`. Use this response for the rejected items — the tracking endpoint mainly reflects accepted items.

## Submit a file for ingestion

Submits the URL of a CSV or NDJSON file. CQA downloads and processes it in the background.

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

**Request body (JSON)**

| Parameter        | Required | Type    | Description                                                                                                                                                                                             |
| ---------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `file_url`       | Yes      | string  | URL to the file. Schemes: `https://` (recommended), `http://` (no transport encryption), `s3://`. Private and local addresses (`localhost`, `127.0.0.1`, `10.x`, `192.168.x`, `172.16.x`) are rejected. |
| `format`         | Yes      | string  | `csv` or `ndjson`. See [6.6 File schemas](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#66-file-schemas).                                                                     |
| `source`         | No       | string  | Default `source` for rows that do not set one.                                                                                                                                                          |
| `pii_redacted`   | No       | boolean | Default PII flag for rows that do not set one.                                                                                                                                                          |
| `callback_url`   | No       | string  | Default callback URL for rows that do not set one.                                                                                                                                                      |
| `column_mapping` | No       | object  | CSV only. Maps your headers to canonical column names. See [Column mapping](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#column-mapping).                                    |
| `metadata`       | No       | object  | Default metadata merged into every row (row keys win). Keep each merged row within the 50-key limit.                                                                                                    |

The response has the same shape as a batch submission (`id`, `type` = `file`, `status` = `pending`). Invalid rows do not fail the request; they are reported as rejected rows in the job results.

## Get an interaction

Returns the current status and details of an ingested interaction.

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

| Path parameter           | Required | Description                                                               |
| ------------------------ | -------- | ------------------------------------------------------------------------- |
| `account_id`             | Yes      | Your CQA Account ID.                                                      |
| `interaction_identifier` | Yes      | Either the CQA `interaction_id` (UUID) or your `external_interaction_id`. |

**Response fields (`data`)**

| Field                     | Type              | Description                                                                                  |
| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------- |
| `interaction_id`          | string (UUID)     | CQA-assigned identifier.                                                                     |
| `external_interaction_id` | string            | Your identifier.                                                                             |
| `batch_id`                | string            | Job ID if the interaction came from a batch or file. Omitted for single ingest.              |
| `channel_type`            | string            | Channel type as submitted.                                                                   |
| `source`                  | string            | Originating system.                                                                          |
| `status`                  | string            | `queued`, `processing`, `completed`, or `failed`.                                            |
| `status_modified_at`      | string (ISO-8601) | When the status last changed.                                                                |
| `failure_reason`          | string            | Why processing failed. Present only when `status` is `failed`.                               |
| `audio_url`               | string            | Resolved audio URL.                                                                          |
| `transcript_url`          | string            | Resolved transcript URL.                                                                     |
| `pii_redacted`            | boolean           | Whether PII was flagged as already redacted.                                                 |
| `created_at`              | string (ISO-8601) | When the interaction was ingested.                                                           |
| `metadata`                | object            | Key-value metadata.                                                                          |
| `analyses`                | array             | Analyses triggered for this interaction. Each has `analysis_id`, `profile_id`, and `status`. |

## Interaction status lifecycle

Applies to `status` on interactions and analyses.

| Status       | Meaning                                    |
| ------------ | ------------------------------------------ |
| `queued`     | Accepted, waiting to be processed.         |
| `processing` | Analysis is underway.                      |
| `completed`  | All analyses finished successfully.        |
| `failed`     | Processing failed. Check `failure_reason`. |

## Job status lifecycle

Applies to `status` in the batch or file `202` response and to `job_status` in the tracking response. Job statuses are separate from interaction statuses.

| Status       | Meaning                                                         |
| ------------ | --------------------------------------------------------------- |
| `pending`    | Job accepted and queued. Returned in the initial response.      |
| `processing` | A worker is processing rows.                                    |
| `completed`  | All rows processed. Check `accepted` and `rejected` for counts. |
| `failed`     | The whole job failed. Check `error_message`.                    |

## Track a batch or file job

Returns the interactions in a batch or file job, with pagination and job-level status.

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

| Path parameter | Required | Description                                                             |
| -------------- | -------- | ----------------------------------------------------------------------- |
| `account_id`   | Yes      | Your CQA Account ID.                                                    |
| `id`           | Yes      | The job `id` returned when you submitted the batch or file (`data.id`). |

| Query parameter | Default | Max | Description                                              |
| --------------- | ------- | --- | -------------------------------------------------------- |
| `page`          | 0       | —   | Zero-based page index.                                   |
| `size`          | 20      | 100 | Interactions per page. Larger values are clamped to 100. |

**Response fields (`data`)**

| Field           | Type              | Description                                                                                                                                                             |
| --------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | string            | The job identifier.                                                                                                                                                     |
| `total`         | integer           | Total interactions in the job.                                                                                                                                          |
| `interactions`  | array             | Paginated interaction detail objects (same fields as [Get an interaction](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#get-an-interaction)). |
| `pagination`    | object            | `page`, `size`, `total_elements`, `total_pages`.                                                                                                                        |
| `job_status`    | string            | `pending`, `processing`, `completed`, or `failed`.                                                                                                                      |
| `job_type`      | string            | `batch` or `file`.                                                                                                                                                      |
| `total_rows`    | integer           | Rows found in the input (accepted + rejected).                                                                                                                          |
| `accepted`      | integer           | Rows processed successfully.                                                                                                                                            |
| `rejected`      | integer           | Rows that failed validation.                                                                                                                                            |
| `errors`        | array             | Up to 100 entries, each with `line` (row number), `reason`, and `external_interaction_id`.                                                                              |
| `error_message` | string            | Top-level error if the whole job failed.                                                                                                                                |
| `completed_at`  | string (ISO-8601) | When the job finished.                                                                                                                                                  |

***

# 8.5. Analysis API

Retrieve quality analysis results.

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

## List analyses

Returns a paginated list of analyses that match your filters. A date filter is required, and the range can be at most **31 days**.

```text
POST https://{host}/cqa/api/v1/accounts/{account_id}/analyses?limit={limit}&offset={offset}
```

| Query parameter | Default | Max   | Description               |
| --------------- | ------- | ----- | ------------------------- |
| `limit`         | 20      | 100   | Results per page.         |
| `offset`        | 0       | 10000 | Zero-based result offset. |

**Request body (JSON)**

| Parameter               | Required | Type            | Description                                                                                  |
| ----------------------- | -------- | --------------- | -------------------------------------------------------------------------------------------- |
| `other_filters`         | Yes      | array           | Filter objects. Must include a date filter.                                                  |
| `quality_profile_uid`   | No       | string          | Filter by Quality Profile UUID.                                                              |
| `metadata_filter_group` | No       | array of arrays | Conditions on interaction metadata. Each inner array is an AND group; the outer array is OR. |

**Date filter (required)**

| Field      | Value                                                                                  |
| ---------- | -------------------------------------------------------------------------------------- |
| `field`    | `"date"`                                                                               |
| `operator` | `"range"`                                                                              |
| `value`    | Object with `start_date` and `end_date` (`YYYY-MM-DD` or ISO-8601). Max range 31 days. |

**Score filter (optional)**

| Field      | Value                                                                                             |
| ---------- | ------------------------------------------------------------------------------------------------- |
| `field`    | `"score"`                                                                                         |
| `operator` | `"greater_than"`, `"less_than"`, `"greater_than_or_equal"`, `"less_than_or_equal"`, or `"equals"` |
| `value`    | Numeric score.                                                                                    |

**Example request**

```bash
curl -X POST "https://{host}/cqa/api/v1/accounts/{account_id}/analyses?limit=5&offset=0" \
  -H "X-API-Key: {your_api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "other_filters": [
      {"field": "date", "operator": "range", "value": {"start_date": "2026-07-01", "end_date": "2026-07-15"}},
      {"field": "score", "operator": "greater_than_or_equal", "value": 9}
    ]
  }'
```

**Example response (`200 OK`)**

```json
{
  "status": 200,
  "request_id": "51f6705d-6639-41ed-9029-d6053c5df1c2",
  "data": {
    "analyses": [
      {
        "analysis_id": "9f77853f-c4d4-482c-9651-b13394d65020",
        "interaction_id": "a30f0175-2ba0-46d8-8002-5b84667bfb44",
        "external_interaction_id": "file_7b1eeafb-f720-40bb-862f-60107f118e93",
        "profile_id": "e7d721ce-035d-4f79-ab62-e9cc2138f449",
        "status": "completed",
        "ai_score": 19.0,
        "qa_score": 21.0,
        "final_score": 21.0,
        "criticality_adjusted_score": 21.0,
        "metadata": {
          "source": "TestEvaluation",
          "agent_id": "qa_user",
          "campaign_id": "100"
        },
        "created_at": "2026-05-29T17:16:46.259993+05:30"
      }
    ],
    "pagination": { "limit": 5, "offset": 0, "total": 1 }
  }
}
```

**Analysis fields**

| Field                        | Type              | Description                                                              |
| ---------------------------- | ----------------- | ------------------------------------------------------------------------ |
| `analysis_id`                | string (UUID)     | Unique analysis identifier.                                              |
| `interaction_id`             | string (UUID)     | The interaction this analysis belongs to.                                |
| `external_interaction_id`    | string            | Your identifier. May be `null` if the interaction record is unavailable. |
| `profile_id`                 | string            | Quality Profile used for scoring.                                        |
| `status`                     | string            | `completed`, `failed`, or `queued`.                                      |
| `ai_score`                   | float             | AI-generated score.                                                      |
| `qa_score`                   | float             | Manual QA score. Omitted if there was no manual review.                  |
| `final_score`                | float             | Effective score: QA score if present, otherwise AI score.                |
| `criticality_adjusted_score` | float             | Score after criticality rules are applied.                               |
| `metadata`                   | object            | Interaction metadata.                                                    |
| `created_at`                 | string (ISO-8601) | When the analysis was created, in the account time zone.                 |

**Pagination fields:** `limit` (requested page size), `offset` (current offset), `total` (matching results across all pages).

**Errors**

| Scenario                      | HTTP | Code               | Example message                                                                                    |
| ----------------------------- | ---- | ------------------ | -------------------------------------------------------------------------------------------------- |
| Missing date filter           | 400  | `VALIDATION_ERROR` | "Date filter is required. Provide a date filter with start\_date and end\_date in other\_filters." |
| Date range over 31 days       | 400  | `VALIDATION_ERROR` | "Date range must not exceed 31 days"                                                               |
| `start_date` after `end_date` | 400  | `VALIDATION_ERROR` | "start\_date must be before or equal to end\_date"                                                 |
| Invalid date format           | 400  | `VALIDATION_ERROR` | "Invalid start\_date format. Expected: YYYY-MM-DD or ISO-8601"                                     |
| Invalid score operator        | 400  | `VALIDATION_ERROR` | "Unsupported score filter operator"                                                                |
| `limit` out of range          | 400  | `VALIDATION_ERROR` | "limit must be between 1 and 100"                                                                  |
| `offset` out of range         | 400  | `VALIDATION_ERROR` | "offset must be between 0 and 10000"                                                               |

## Get analysis detail

Returns the full scoring breakdown for one analysis: categories, sub-categories, and KPIs.

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

| Path parameter | Required | Description                                                                                                                                                                               |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account_id`   | Yes      | Your CQA Account ID.                                                                                                                                                                      |
| `analysis_id`  | Yes      | The analysis UUID (from the `analyses` array in [Get an interaction](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#get-an-interaction), or from List analyses). |

**Response fields (`data`)**

| Field                        | Type              | Description                                                                                                                                                                                         |
| ---------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `analysis_id`                | string (UUID)     | Unique analysis identifier.                                                                                                                                                                         |
| `interaction_id`             | string (UUID)     | The interaction this analysis belongs to.                                                                                                                                                           |
| `external_interaction_id`    | string            | Your identifier.                                                                                                                                                                                    |
| `profile_id`                 | string            | Quality Profile used for scoring.                                                                                                                                                                   |
| `profile_name`               | string            | Quality Profile name.                                                                                                                                                                               |
| `status`                     | string            | `queued`, `processing`, `completed`, or `failed`.                                                                                                                                                   |
| `ai_score`                   | float             | AI-generated score.                                                                                                                                                                                 |
| `qa_score`                   | float             | Manual QA score. Omitted if no manual review has occurred.                                                                                                                                          |
| `final_score`                | float             | QA score if present, otherwise AI score.                                                                                                                                                            |
| `criticality_adjusted_score` | float             | Score after criticality rules are applied.                                                                                                                                                          |
| `max_score`                  | float             | Maximum possible score for this profile.                                                                                                                                                            |
| `analysis_completed_at`      | string (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_reason`             | string            | Not populated in the current response (null omitted). Use status and support channels when an analysis fails.                                                                                       |
| `categories`                 | array             | Scored categories (see below).                                                                                                                                                                      |
| `metadata`                   | object            | Interaction metadata.                                                                                                                                                                               |

**Category object**

| Field                        | Type   | Description                                          |
| ---------------------------- | ------ | ---------------------------------------------------- |
| `name`                       | string | Category name (for example, "Communication Skills"). |
| `ai_score`                   | float  | AI score for the category.                           |
| `qa_score`                   | float  | Manual QA score. Omitted if not set.                 |
| `final_score`                | float  | Final category score.                                |
| `criticality_adjusted_score` | float  | Criticality-adjusted score.                          |
| `max_score`                  | float  | Maximum possible score.                              |
| `sub_categories`             | array  | Sub-categories in this category.                     |

**Sub-category object**

| Field      | Type   | Description                          |
| ---------- | ------ | ------------------------------------ |
| `name`     | string | Sub-category name.                   |
| `qa_score` | float  | Manual QA score. Omitted if not set. |
| `kpis`     | array  | KPIs scored in this sub-category.    |

**KPI object**

| Field                          | Type   | Description                                              |
| ------------------------------ | ------ | -------------------------------------------------------- |
| `kpi_name`                     | string | KPI name (for example, "Proper Greeting").               |
| `ai_response`                  | string | The AI's answer (for example, "Yes", "No", "Partially"). |
| `ai_justification`             | string | The AI's reasoning.                                      |
| `ai_suggestion`                | string | AI coaching suggestion. Omitted if not set.              |
| `ai_score`                     | float  | AI score for the KPI.                                    |
| `qa_score`                     | float  | Manual QA score. Omitted if not set.                     |
| `qa_justification`             | string | QA justification. Omitted if not set.                    |
| `qa_selected_kpi_option_uid`   | string | UID of the KPI option the reviewer selected, if any.     |
| `qa_selected_kpi_option_value` | string | Label of the KPI option the reviewer selected.           |
| `user_comment`                 | string | Free-text reviewer comment.                              |
| `final_score`                  | float  | Final KPI score (may reflect a QA override).             |
| `criticality_adjusted_score`   | float  | Criticality-adjusted score.                              |
| `max_score`                    | float  | Maximum possible score for the KPI.                      |

***

# 8.6. File schemas

The file endpoint ([Submit a file for ingestion](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#submit-a-file-for-ingestion)) accepts **CSV** and **NDJSON**. The same CSV layout is used when you import files through an S3 or SFTP connector ([Customer S3](https://docs.exotel.com/conversation-intelligence/5-manage-data-integration#customer-s3), [Customer SFTP](https://docs.exotel.com/conversation-intelligence/5-manage-data-integration#customer-sftp)).

Every row needs at least one of `audio_url`, `transcript_url`, or `transcript_text`. Recording and transcript URLs must be publicly accessible or pre-signed.

## CSV schema

The first row must contain column headers. Headers are trimmed and lowercased before they are matched to the canonical names below.

| Column                    | Required                         | Type                   | Description                                                                                                                                                |
| ------------------------- | -------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_interaction_id` | Yes                              | string                 | Your unique interaction identifier.                                                                                                                        |
| `channel_type`            | Yes                              | string                 | `VOICE`, `CHAT`, `EMAIL`, `SMS`, or `WHATSAPP`.                                                                                                            |
| `source`                  | No                               | string                 | Originating system identifier.                                                                                                                             |
| `language`                | No                               | string                 | Language code (for example, `en`).                                                                                                                         |
| `interaction_start_time`  | No                               | string (ISO-8601, UTC) | For example, `2026-04-01T10:00:00Z`.                                                                                                                       |
| `duration_seconds`        | No                               | integer                | Interaction duration in seconds.                                                                                                                           |
| `audio_format`            | No                               | string                 | Format hint (for example, `WAV`, `MP3`).                                                                                                                   |
| `callback_url`            | No                               | string                 | Per-row callback URL. See [6.7](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#67-webhooks-callbacks).                            |
| `pii_redacted`            | No                               | boolean                | `true` or `false`.                                                                                                                                         |
| `audio_url`               | If no transcript                 | string                 | Audio file URL(s). Separate multiple URLs with `;`.                                                                                                        |
| `transcript_url`          | If no audio or `transcript_text` | string                 | Transcript file URL(s). Separate multiple URLs with `;`.                                                                                                   |
| `transcript_text`         | If no audio or `transcript_url`  | string                 | Transcript text. If `transcript_url` is also set, `transcript_url` is used.                                                                                |
| `file_url`                | No                               | string                 | Generic file URL, used with `file_type` when no `audio_url` or `transcript_url` is set.                                                                    |
| `file_type`               | No                               | string                 | File extension for type resolution. Audio: `mp3`, `wav`, `ogg`, `flac`, `m4a`, `aac`, `wma`, `amr`. Transcript: `txt`, `pdf`, `doc`, `docx`, `srt`, `vtt`. |

## Extra columns become metadata

Any header that is not a canonical column is added to the row's metadata. For example, columns named `agent`, `campaign`, or `disposition` become metadata keys with no extra configuration. To use those fields for filters, routing, access control, or AI context, configure them in [5.2 Metadata](https://docs.exotel.com/conversation-intelligence/5-manage-data-integration#52-metadata).

## Column mapping

If your CSV uses different headers, send a `column_mapping` object with the file request. Keys are your original headers (trimmed, lowercased); values are canonical names.

For a CSV with headers `call_id,type,recording,agent,campaign`:

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

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

## Example CSV

```text
external_interaction_id,channel_type,audio_url,transcript_url,transcript_text,language,agent,campaign
call-001,VOICE,https://s3.example.com/rec-001.wav,,,en,agent-42,retention
call-002,VOICE,https://s3.example.com/rec-002.wav,,,hi,agent-15,support
chat-001,CHAT,,,"Agent: Hello! How can I help you today?\nCustomer: I need to reset my password.",en,agent-42,retention
call-003,CHAT,,,,,agent-42,retention
```

- `call-001` and `call-002` are valid through `audio_url`.
- `chat-001` is valid through `transcript_text`.
- `call-003` is rejected because it has no `audio_url`, `transcript_url`, or `transcript_text`.

## NDJSON schema

NDJSON (newline-delimited JSON) has one JSON object per line. Each object uses the same fields as the [single ingest](https://docs.exotel.com/conversation-intelligence/6-api-reference-guide#ingest-a-single-interaction) request body, including a nested `metadata` object. Blank lines are skipped; a line that is not valid JSON is reported as a rejected row. `column_mapping` does not apply to NDJSON.

```text
{"external_interaction_id":"call-001","channel_type":"VOICE","audio_url":"https://s3.example.com/rec-001.wav","language":"en","metadata":{"agent":"agent-42","campaign":"retention"}}
{"external_interaction_id":"chat-001","channel_type":"CHAT","transcript_text":"Agent: Hello! How can I help you today?\nCustomer: I need to reset my password.","language":"en","metadata":{"agent":"agent-42"}}
```

## Request-level defaults

For both CSV and NDJSON, fields on the file request apply to every row:

| Request field  | Behavior                                                         |
| -------------- | ---------------------------------------------------------------- |
| `source`       | Applied to rows where `source` is null or empty.                 |
| `pii_redacted` | Applied to rows where the value is null.                         |
| `callback_url` | Applied to rows where the value is null.                         |
| `metadata`     | Merged with each row's metadata. Row-level keys take precedence. |

***

# 8.7. Webhooks / callbacks

When a `callback_url` is set — on a single ingest, on a batch or file request, or per row — CQA sends HTTP POST notifications to that URL at key points in the interaction's or file job's lifecycle.

**Enablement.** Callback delivery is turned on per account by Exotel. Until it is on, `callback_url` values are stored but no requests are sent. Contact your Customer Success Manager to enable it.

## Delivery

- **Method:** HTTP POST
- **Content-Type:** `application/json`
- **Timeout:** 10 seconds (connect and read)

## Event types

| Event                              | Trigger                                                                                                                                              |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INTERACTION_INGESTED`             | The interaction has been accepted and stored.                                                                                                        |
| `INTERACTION_ANALYSIS_IN_PROGRESS` | Analysis has started for a Quality Profile.                                                                                                          |
| `INTERACTION_ANALYSIS_COMPLETED`   | One analysis completed. Payload includes scores and KPI results.                                                                                     |
| `INTERACTION_ANALYSIS_FAILED`      | One analysis failed. Payload includes error details.                                                                                                 |
| `INTERACTION_DISPUTE_RAISED`       | A dispute was raised on an analysis. See [3.9 Disputes](https://docs.exotel.com/conversation-intelligence/3-work-operational-workflows#39-disputes). |
| `INTERACTION_DISPUTE_RESOLVED`     | A dispute was resolved.                                                                                                                              |
| `FILE_INGESTION_COMPLETED`         | A file ingestion job finished processing.                                                                                                            |

## Payload structure

Every callback body is a JSON object with these top-level fields:

| Field                   | Type              | Description                                                                |
| ----------------------- | ----------------- | -------------------------------------------------------------------------- |
| `event`                 | string            | The event type.                                                            |
| `deliveryId`            | string (UUID)     | Unique identifier for this delivery attempt.                               |
| `timestamp`             | string (ISO-8601) | When the callback was generated (for example, `2026-04-01T10:35:42.123Z`). |
| `accountId`             | string            | Your account identifier.                                                   |
| `interactionId`         | string (UUID)     | CQA's interaction identifier. Present for interaction-level events.        |
| `externalInteractionId` | string            | Your `external_interaction_id`, when available.                            |
| `data`                  | object            | Event-specific data (below).                                               |

## Data by event type

`INTERACTION_INGESTED`

```json
{ "status": "INGESTED" }
```

`INTERACTION_ANALYSIS_IN_PROGRESS`

```json
{
  "analysisId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "profileId": "prof-001",
  "profileName": "Inbound Support"
}
```

`INTERACTION_ANALYSIS_COMPLETED` — `kpiResults` is included when KPI-level results are available. Each entry has the KPI, its category and sub-category, the AI's response and justification, and the scores.

```json
{
  "analysisId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "COMPLETED",
  "profileId": "prof-001",
  "aiScore": 85.0,
  "qaScore": null,
  "finalScore": 85.0,
  "criticalityAdjustedScore": 85.0,
  "analysisCompletedAt": "2026-04-01T10:35:42Z",
  "kpiResults": [
    {
      "kpiId": "kpi-101",
      "categoryId": "cat-01",
      "subCategoryId": "subcat-01",
      "aiResponse": "Yes",
      "aiJustification": "The agent greeted the customer by name.",
      "aiScore": 5.0,
      "qaScore": null,
      "finalScore": 5.0,
      "criticalityAdjustedScore": 5.0
    }
  ]
}
```

`INTERACTION_ANALYSIS_FAILED`

```json
{
  "errorCode": "ANALYSIS_FAILED",
  "errorMessage": "Transcript processing timed out"
}
```

`INTERACTION_DISPUTE_RAISED`

```json
{
  "analysisId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "disputeId": "d1e2f3a4-b5c6-7890-abcd-ef1234567890"
}
```

`INTERACTION_DISPUTE_RESOLVED`

```json
{
  "analysisId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "disputeId": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
  "resolution": "ACCEPTED",
  "updatedScore": 90.0
}
```

`FILE_INGESTION_COMPLETED`

```json
{
  "fileJobId": "job-2026-04-01-001",
  "status": "FILE_INGESTION_COMPLETED",
  "totalRows": 500,
  "accepted": 498,
  "rejected": 2,
  "completedAt": "2026-04-01T11:05:00Z",
  "errors": [
    { "row": 42, "reason": "Missing required field: external_interaction_id" }
  ]
}
```

## Full example

A complete callback for a completed analysis:

```json
{
  "event": "INTERACTION_ANALYSIS_COMPLETED",
  "deliveryId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "timestamp": "2026-04-01T10:35:42.123Z",
  "accountId": "{account_id}",
  "interactionId": "550e8400-e29b-41d4-a716-446655440000",
  "externalInteractionId": "call-2026-04-01-001",
  "data": {
    "analysisId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "COMPLETED",
    "profileId": "prof-001",
    "aiScore": 85.0,
    "qaScore": null,
    "finalScore": 85.0,
    "criticalityAdjustedScore": 85.0,
    "analysisCompletedAt": "2026-04-01T10:35:42Z",
    "kpiResults": [
      {
        "kpiId": "kpi-101",
        "categoryId": "cat-01",
        "subCategoryId": "subcat-01",
        "aiResponse": "Yes",
        "aiJustification": "The agent greeted the customer by name.",
        "aiScore": 5.0,
        "qaScore": null,
        "finalScore": 5.0,
        "criticalityAdjustedScore": 5.0
      }
    ]
  }
}
```

## Verify the signature (HMAC)

Each callback includes headers you can use to check that it came from CQA:

| Header            | Description                                                                   |
| ----------------- | ----------------------------------------------------------------------------- |
| `X-CQA-Signature` | HMAC-SHA256 of the request body, formatted as `sha256=<hex>`.                 |
| `X-CQA-Timestamp` | ISO-8601 time the request was sent (for example, `2026-04-01T10:35:42.123Z`). |

To verify a callback:

1. Take the hex digest from `X-CQA-Signature` (remove the `sha256=` prefix).
2. Compute `HMAC-SHA256(secret, requestBody)` with your signing secret.
3. Compare your digest with the one from step 1.

The signing secret is derived from your active API key and is shared with you during onboarding. If your account has two active keys, or you rotate a key, confirm the current signing secret with CQA Support.

## Retry policy

If your endpoint returns `5xx` or `429`, or the request times out, CQA retries. There are 3 attempts in total:

| Attempt         | Delay before attempt |
| --------------- | -------------------- |
| 1st (initial)   | Immediate            |
| 2nd (1st retry) | About 10 seconds     |
| 3rd (2nd retry) | About 30 seconds     |

After 3 failed attempts, the delivery is marked `FAILED`. Responses with `2xx` or `4xx` (other than `429`) are not retried.

***

# 8.8. Limits

This is the single reference for API limits. Defaults are typical per account (tenant) and can differ by plan.

## Rate limits

Requests over the limit receive `429 Too Many Requests` with `RATE_LIMIT_EXCEEDED`. Too many concurrent file jobs is a separate `429` with `TOO_MANY_JOBS`.

| Endpoint pattern                  | Method | Default limit           |
| --------------------------------- | ------ | ----------------------- |
| `/ingress/interactions*` (ingest) | POST   | 100 requests per minute |
| `/ingress/**` (tracking)          | GET    | 300 requests per minute |

## Request and file limits

| Constraint                       | Value                                                               |
| -------------------------------- | ------------------------------------------------------------------- |
| Interactions per batch           | 1–100                                                               |
| Metadata keys per interaction    | 50 (keep merged file-row metadata within this)                      |
| `transcript_text` size           | 100 KB (UTF-8)                                                      |
| Rows per file job                | 100,000                                                             |
| File size per file job           | 100 MB                                                              |
| Concurrent file jobs per account | 5 (default)                                                         |
| File formats                     | `csv`, `ndjson`                                                     |
| File URL schemes                 | `https` (recommended), `http`, `s3`                                 |
| Channel types                    | `VOICE`, `CHAT`, `EMAIL`, `SMS`, `WHATSAPP` (other values accepted) |
| Batch tracking page size         | 100 max (larger values clamped)                                     |
| List analyses date range         | 31 days max                                                         |
| List analyses `limit` / `offset` | 100 / 10,000 max                                                    |

## Conversation caps

Each account has daily, weekly, or monthly conversation caps. When a cap is reached, ingest requests return `429 CONVERSATION_CAP_EXCEEDED` until the period resets or the plan changes. Trial accounts with no explicit cap default to **300 conversations per month**.

Admins can see caps and usage in footer **Usage & Limit** ([7.1 Usage & Limit](https://docs.exotel.com/conversation-intelligence/7-usage-policies-and-support#71-usage-limit)) and can set up warning emails in [Alert & Notification](https://docs.exotel.com/conversation-intelligence/4-manage-configuration#46-alert-notification).
