Audio Ingestion API

The Audio Ingestion API uploads an audio recording (e.g., a call recording from a telephony provider) to be processed by an Agent QA AI Employee. Once uploaded, the file is transcribed, diarized, evaluated against your QA parameters, and made available in the Audit tab and the daily export.

Use this endpoint when you have an audio file (.mp3 or .wav). If you already have a text transcript, use the Transcript Ingestion API instead.

11.1 Authentication & Headers

For instructions on generating a Bearer token from your API key, see the internal Authentication Guide for Agent QA.

All requests require the following headers:

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token (e.g., Bearer <token>)
x-persona-idUUIDYesThe unique identifier of the Agent QA AI Employee (persona). Found in the URL of the Ema web app, e.g., https://staging.ema.co/ai-employees/<persona_id>.

11.2 Upload File

Uploads an audio file for processing by the AI Employee.

PropertyValue
URL{base_url}/api/v1/external/upload/file
HTTP MethodPOST
Content-Typemultipart/form-data

The {base_url} is region-specific (for example, a staging EU tenant uses https://api.staging.tp-eu.ema.co). Confirm the correct host for your deployment with your Ema contact.

Form-Data Fields

FieldTypeRequiredDescription
filefileYesThe audio file to upload. Supported formats: .mp3, .wav.
agent_idstringYesIdentifier of the human agent associated with the recording.
agent_emailstringYesThe human agent's email. Used for permissioning so agents can only view their own calls.
resource_idstringYesExternal identifier (e.g., a Call ID from your telephony provider).
languagestringNoLanguage of the audio file.
channelsintegerYesNumber of channels in the call (Mono = 1, Dual/Stereo = 2).
channel_mapJSONYesFor stereo calls, which channel maps to which speaker. Example: {"Agent": 0, "Customer": 1}.
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.
metadataJSONNoAny additional metadata you want attached to the call record.

Constraints

  • Maximum file size: 25 MB.
  • Allowed MIME types: audio/mpeg, audio/wav.

Success Response (200 OK)

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

11.3 Error Codes

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

11.4 Rate Limits

A rate limit of 10 queries per second is enforced on the data upload API. When rate limited, the response includes a Retry-After header (in seconds). Clients should retry with exponential backoff.

11.5 Sample Python

The script below explicitly sets the MIME type to audio/mpeg. Use audio/wav if uploading WAV files. The x-persona-id header is the source of truth for which AI Employee the data belongs to.

import requests
import time

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_audio_file(file_path, agent_id, agent_email, resource_id,
                     language, channels, channels_map, timestamp,
                     max_retries=3):
    """Uploads an audio file for a specific AI Employee."""
    url = f"{BASE_URL}/api/v1/external/upload/file"
    data = {
        "agent_id": agent_id,
        "agent_email": agent_email,
        "resource_id": resource_id,
        "language": language,
        "channels": channels,
        "channels_map": channels_map,
        "timestamp": timestamp,
    }
    try:
        with open(file_path, "rb") as f:
            files = {"file": (file_path, f, "audio/mpeg")}  # or audio/wav
            for attempt in range(max_retries + 1):
                response = requests.post(url, headers=headers, data=data, files=files)
                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
    except FileNotFoundError:
        print(f"Error: File {file_path} not found.")
        return None

if __name__ == "__main__":
    result = upload_audio_file(
        file_path="sample-3s.mp3",
        agent_id="human_agent_001",
        agent_email="[email protected]",
        resource_id="resource_id_123",
        language="en",
        channels=2,
        channels_map={"Agent": 0, "Customer": 1},
        timestamp=time.time(),
    )
    if result:
        print(f"New File ID: {result.get('file_id')}")

Last updated: Jul 3, 2026