Daily Data Export API

The Daily Data Export API returns presigned download URLs for an AI Employee's Agent QA daily export and its manifest file. The export is generated nightly by Ema's internal workflow and covers all calls processed on the specified UTC date.

Use this API to pull evaluation results, scorecards, and insights into your own data warehouse, BI tool, or downstream analytics system.

13.1 Authentication & Headers

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token (e.g., Bearer <token>)

13.2 Get Daily Export Data

PropertyValue
URL{base_url}/api/agent-qa/get_daily_data
HTTP MethodGET

Query Parameters

FieldTypeRequiredDescription
persona_idUUIDYesThe unique identifier of the AI Employee (persona). Found in the URL of the Ema web app.
datestringYesUTC date to retrieve, in YYYY-MM-DD format. Must be strictly in the past (UTC). Today or future dates are rejected.

Example Request

curl -X GET \
  "{base_url}/api/agent-qa/get_daily_data?persona_id=<persona_id>&date=2025-06-01" \
  -H "Authorization: Bearer <token>"

Response

A successful response (200 OK) returns a JSON object with the following fields:

FieldTypeDescription
signed_urlstringPresigned download URL for the daily data export file (JSONL format).
manifest_signed_urlstringPresigned download URL for the manifest JSON file.
expires_in_secondsintegerNumber of seconds until the presigned URLs expire. Currently 3600 (1 hour).

13.3 File Structure

The two presigned URLs point to files stored in Ema's internal cloud storage. One is a data file containing the actual records and results. The other is a manifest file describing the export partition. URLs expire within an hour; re-request if needed.

Data File (signed_urlagent_qa_export.jsonl)

The data file is JSONL (newline-delimited JSON). Each line is a self-contained JSON object representing one processed call record, corresponding to a single resource_id. The schema is defined by AgentQACallRecord.

FieldTypeRequiredDescription
resource_idstringYesUnique external call identifier sent by the client (e.g., a Call ID).
agent_idstringYesIdentifier of the human agent involved in the call.
agent_emailstringNoEmail of the agent. Used for permissioning.
call_timetimestampYesISO 8601 UTC timestamp of when the call occurred.
persona_idstringYesIdentifier of the Agent QA AI Employee that processed this call.
final_scorefloatNoCalculated QA score on a 0–100 scale. Null if evaluation did not complete.
sentimentenumNoOverall sentiment. One of: POSITIVE, NEGATIVE, NEUTRAL.
resolution_statusenumNoWhether the issue was resolved. One of: RESOLVED, NOT_RESOLVED.
contact_reason_categorylist[string]NoOrdered list of categories explaining why the customer contacted support.
call_driver_reasonstringNoDetailed free-text reason for the call.
non_resolution_reason_categorylist[string]NoReasons the call was not resolved. Only populated when resolution_status is NOT_RESOLVED.
agent_talk_timefloatNoDuration (seconds) the agent was speaking.
user_talk_timefloatNoDuration (seconds) the user was speaking.
dead_airfloatNoDuration (seconds) of silence/dead air.
total_audio_timefloatNoTotal audio duration (seconds).
case_idstringNoTicket/case ID associated with the call.
agent_tenureintegerNoAgent tenure in days.
rule_resultslist[object]YesList of QA rule evaluation results. See below.
insightslist[object]YesList of behavioural insights. See below.

Insight Attributes

FieldTypeDescription
insight_typeenumOne of: STRENGTH, WEAKNESS, BUG, FEEDBACK, COMPETITOR.
driverstringHigh-level behavioural driver (e.g., "Information verification").
categorystringSub-category within the driver (e.g., "Active listening").
justificationstringExplanation for why this insight was surfaced.
verbatimstringTranscript snippet supporting this insight.

Rule Result Attributes

FieldTypeDescription
rule_namestringName of the QA rule being evaluated.
rule_categorystringGrouping of rules (e.g., "Customer experience", "Call management").
rule_weightfloatWeighting of this rule when calculating the final score.
is_criticalboolWhether this is a critical rule. If any critical rule fails, the recording receives a final score of 0.
rule_mechanismenumInformation source used. One of: TRANSCRIPT, TRANSCRIPT_WITH_KNOWLEDGE_BASE.
resultenumOne of: PASS, FAIL, NOT_APPLICABLE.
rationalestringExplanation of why this rule passed, failed, or was not applicable.
verbatimslist[string]Transcript snippets supporting the evaluation result.

Manifest File (manifest_signed_urlmanifest.json)

The manifest file is a single JSON object describing the export partition. It is written alongside the data file after the nightly export completes successfully. The schema is defined by AgentQAExportManifest.

FieldTypeDescription
tenant_idstringThe Ema tenant that owns this export.
persona_idstringThe AI Employee persona for this export partition.
datestringCalendar date of the export partition in YYYY-MM-DD format.
window_startstringISO 8601 UTC start of the export window (inclusive).
window_endstringISO 8601 UTC end of the export window (exclusive).
row_countintegerNumber of call records written to the data file.

13.4 Error Codes

HTTP CodeReason
400 Bad RequestInvalid persona_id format or other malformed request parameters.
401 UnauthorizedMissing or invalid Authorization header.
403 ForbiddenCredentials do not have edit-level permission to access the specified persona.
404 Not FoundPersona does not exist, or no export is available for the requested date.
422 Unprocessable Entitydate is not in YYYY-MM-DD format, or is today/future (must be a past UTC date).
500 Internal Server ErrorUnexpected server-side error.

13.5 Rate Limits

A rate limit of 1 query per second is enforced on this API.

13.6 Sample Python

The snippet below downloads and parses agent_qa_export.jsonl from the presigned URL returned by the API.

import json
import urllib.request

# Step 1 — call the API to get the presigned URLs
req = urllib.request.Request(
    "https://api.<region>.ema.co/api/agent-qa/get_daily_data"
    "?persona_id=<persona_id>&date=2025-06-01",
    headers={
        "Authorization": "Bearer <token>",
        "x-persona-id": "<persona_id>",
    },
)
with urllib.request.urlopen(req) as resp:
    urls = json.load(resp)

signed_url = urls["signed_url"]
manifest_signed_url = urls["manifest_signed_url"]

# Step 2 — download and parse the JSONL data file
records = []
with urllib.request.urlopen(signed_url) as resp:
    for line in resp:
        line = line.strip()
        if line:
            records.append(json.loads(line))

# Step 3 — work with the records
for record in records:
    resource_id  = record["resource_id"]
    agent_id     = record["agent_id"]
    final_score  = record.get("final_score")
    sentiment    = record.get("sentiment")
    resolution   = record.get("resolution_status")
    rule_results = record["rule_results"]
    insights     = record["insights"]

    print(f"{resource_id} | agent={agent_id} | score={final_score} | {sentiment} | {resolution}")

    for rule in rule_results:
        print(f"  [{rule['result']}] {rule['rule_name']} (weight={rule['rule_weight']})")

    for insight in insights:
        print(f"  [{insight['insight_type']}] {insight['driver']}{insight['category']}")

# Step 4 — download and parse the manifest
with urllib.request.urlopen(manifest_signed_url) as resp:
    manifest = json.load(resp)

print(f"Export date : {manifest['date']}")
print(f"Window      : {manifest['window_start']}{manifest['window_end']}")
print(f"Row count   : {manifest['row_count']}")

Last updated: Jul 3, 2026