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.

13.1 Authentication & Headers

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

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token (e.g., Bearer <token>)
x-persona-idUUIDYesThe unique identifier of the Agent QA AI Employee (persona).

13.2 Upload Transcript

PropertyValue
URL{base_url}/api/v1/external/upload/transcript
HTTP MethodPOST
Content-Typeapplication/json

Body Fields

FieldTypeRequiredDescription
formatstringYesFormat of raw_transcript. Supported values: json, xml, html.
raw_transcriptstringYesFull transcript serialised as a string in the specified format. See format examples below.
agent_idstringYesIdentifier of the human agent associated with the recording.
agent_emailstringYesThe human agent's email. Used for permissioning.
resource_idstringYesExternal identifier (e.g., Call ID from your telephony provider).
languagestringNoLanguage of the transcript.
timestampstringYesCall timestamp. Values without timezone information are treated as UTC.
case_idstringNoTicket/case ID associated with the call.
customer_surveyJSONNoCustomer satisfaction/dissatisfaction survey data.
agent_tenureintegerNoTenure 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.

[
  {
    "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)

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

13.3 Error Codes

HTTP CodeReason
400 Bad RequestMissing headers, invalid UUID format, unsupported format, or missing agent_id.
403 ForbiddenCredentials do not have permission to access the specified AI Employee.
429 Too Many RequestsRate limit exceeded. Retry with exponential backoff.
500 Internal Server ErrorUnexpected server-side error.

13.4 Post-Upload Processing Failures

A 200 OK response confirms the file was accepted for processing, but processing failures can still occur asynchronously. One category of failure is specific to transcript ingestion:

CORRUPT_TRANSCRIPT

If the uploaded file carries content but cannot be decoded into utterances, the interaction is failed with the terminal audit state CORRUPT_TRANSCRIPT. Examples that trigger this state:

  • A json format upload whose body is not a JSON array of {time, role, text} objects.
  • A file whose format value does not match the actual contents.

CORRUPT_TRANSCRIPT is permanent. Decoding is deterministic: the same bytes will always fail. The interaction will not be retried automatically. You must correct the source file and resubmit it via a new upload request.

This state is distinct from EMPTY_TRANSCRIPT, which is a well-formed transcript that simply contains no utterances.

13.5 Rate Limits

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

13.6 Sample 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="[email protected]",
        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')}")

Last updated: Nov 5, 2026