> Source: https://builder-docs.ema.ai/agent-qa/api-transcript-ingestion
> Title: Transcript Ingestion API

# Transcript Ingestion API

The Transcript Ingestion API uploads a pre-existing text transcript for processing by the Agent QA AI Employee. Use this endpoint when audio is not available and you already have the conversation transcript in JSON, XML, or HTML format.

If you have an audio file instead, use the [Audio Ingestion API](/legacy-docs/agent-qa/api-audio-ingestion).

## 13.1 Authentication & Headers

All requests require the same headers as the Audio Ingestion API:

Header

Type

Required

Description

`Authorization`

string

Yes

Bearer token (e.g., `Bearer <token>`)

`x-persona-id`

UUID

Yes

The unique identifier of the Agent QA AI Employee (persona).

## 13.2 Upload Transcript

Property

Value

URL

`{base_url}/api/v1/external/upload/transcript`

HTTP Method

POST

Content-Type

`application/json`

### Body Fields

Field

Type

Required

Description

`format`

string

Yes

Format of `raw_transcript`. Supported values: `json`, `xml`, `html`.

`raw_transcript`

string

Yes

Full transcript serialised as a string in the specified `format`. See format examples below.

`agent_id`

string

Yes

Identifier of the human agent associated with the recording.

`agent_email`

string

Yes

The human agent's email. Used for permissioning.

`resource_id`

string

Yes

External identifier (e.g., Call ID from your telephony provider).

`language`

string

No

Language of the transcript.

`timestamp`

string

Yes

Call timestamp. Values without timezone information are treated as UTC.

`case_id`

string

No

Ticket/case ID associated with the call.

`customer_survey`

JSON

No

Customer satisfaction/dissatisfaction survey data.

`agent_tenure`

integer

No

Tenure of the associated agent in days.

### `json` format

A JSON array where each element is one utterance. The `time` field is optional and accepts ISO 8601, `HH:MM:SS`, `HH:MM`, and common date-time variants. When omitted, all utterances are assigned a timestamp of 0.

```json
[
  {
    "time": "09:15:00",
    "role": "Agent",
    "text": "Hello, how can I help you?"
  },
  {
    "time": "09:15:05",
    "role": "Customer",
    "text": "I have an issue with my account."
  }
]
```

### `xml` or `html` format

An XML chat document. The `time` attribute is optional.

```
<chat>
  <message time="09:15:00" role="Agent">Hello, how can I help you?</message>
  <message time="09:15:05" role="Customer">I have an issue with my account.</message>
</chat>
```

### Success Response (`200 OK`)

```json
{
  "file_id": "550e8400-e29b-41d4-a716-446655440000",
  "file_name": "resource_id_123",
  "status": "UPLOAD_COMPLETED"
}
```

## 13.3 Error Codes

HTTP Code

Reason

`400 Bad Request`

Missing headers, invalid UUID format, unsupported format, or missing `agent_id`.

`403 Forbidden`

Credentials do not have permission to access the specified AI Employee.

`429 Too Many Requests`

Rate limit exceeded. Retry with exponential backoff.

`500 Internal Server Error`

Unexpected server-side error.

## 13.4 Rate Limits

A rate limit of **10 queries per second** is enforced on the data upload API.

## 13.5 Sample Python

```python
import requests
import time
import json

BASE_URL = "https://api.<region>.ema.co"  # replace with your domain
AUTH_TOKEN = "YOUR_BEARER_TOKEN"
AI_EMPLOYEE_ID = "YOUR_AI_EMPLOYEE_UUID"

headers = {
    "Authorization": f"Bearer {AUTH_TOKEN}",
    "x-persona-id": AI_EMPLOYEE_ID,
}

def upload_transcript(transcript, agent_id, agent_email, resource_id,
                      timestamp, max_retries=3):
    """Uploads a text transcript for a specific AI Employee.

    transcript: list of dicts with keys: time (optional), role, text.
    """
    url = f"{BASE_URL}/api/v1/external/upload/transcript"
    payload = {
        "format": "json",
        "raw_transcript": json.dumps(transcript),
        "agent_id": agent_id,
        "agent_email": agent_email,
        "resource_id": resource_id,
        "timestamp": timestamp,
    }
    for attempt in range(max_retries + 1):
        response = requests.post(url, headers=headers, json=payload)
        if response.status_code == 200:
            return response.json()
        if response.status_code == 429 and attempt < max_retries:
            retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
            time.sleep(retry_after)
            continue
        print(f"Failed! Status: {response.status_code}")
        print(f"Error: {response.text}")
        return None

if __name__ == "__main__":
    result = upload_transcript(
        transcript=[
            {"time": "09:15:00", "role": "Agent",    "text": "Hello, how can I help you?"},
            {"time": "09:15:05", "role": "Customer", "text": "I have an issue with my account."},
            {"time": "09:15:10", "role": "Agent",    "text": "I'd be happy to help with that."},
        ],
        agent_id="human_agent_001",
        agent_email="human_agent_001@email.com",
        resource_id="resource_id_456",
        timestamp=time.strftime("%Y-%m-%d %H:%M:%S"),
    )
    if result:
        print(f"New File ID: {result.get('file_id')}")
```
