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:
| 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). 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.
| Property | Value |
|---|---|
| URL | {base_url}/api/v1/external/upload/file |
| HTTP Method | POST |
| Content-Type | multipart/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
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The audio file to upload. Supported formats: .mp3, .wav. |
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 so agents can only view their own calls. |
resource_id | string | Yes | External identifier (e.g., a Call ID from your telephony provider). |
language | string | No | Language of the audio file. |
channels | integer | Yes | Number of channels in the call (Mono = 1, Dual/Stereo = 2). |
channel_map | JSON | Yes | For stereo calls, which channel maps to which speaker. Example: {"Agent": 0, "Customer": 1}. |
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. |
metadata | JSON | No | Any 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 Code | Reason |
|---|---|
400 Bad Request | Missing headers, invalid UUID format, unsupported file extension, or missing agent_id. |
403 Forbidden | Credentials do not have permission to access the specified AI Employee. |
413 Payload Too Large | The uploaded file exceeds the size limit. |
429 Too Many Requests | Rate limit exceeded. Retry with exponential backoff. |
500 Internal Server Error | Unexpected 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')}")