> Source: https://builder-docs.ema.ai/agent-qa/api-daily-export
> Title: Daily Data Export API

# 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.

## 14.1 Authentication & Headers

Header

Type

Required

Description

`Authorization`

string

Yes

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

## 14.2 Get Daily Export Data

Property

Value

URL

`{base_url}/api/agent-qa/get_daily_data`

HTTP Method

GET

### Query Parameters

Field

Type

Required

Description

`persona_id`

UUID

Yes

The unique identifier of the AI Employee (persona). Found in the URL of the Ema web app.

`date`

string

Yes

UTC date to retrieve, in `YYYY-MM-DD` format. Must be strictly in the past (UTC). Today or future dates are rejected.

### Example Request

```bash
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:

Field

Type

Description

`signed_url`

string

Presigned download URL for the daily data export file (JSONL format).

`manifest_signed_url`

string

Presigned download URL for the manifest JSON file.

`expires_in_seconds`

integer

Number of seconds until the presigned URLs expire. Currently 3600 (1 hour).

## 14.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_url` → `agent_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`.

Field

Type

Required

Description

`resource_id`

string

Yes

Unique external call identifier sent by the client (e.g., a Call ID).

`agent_id`

string

Yes

Identifier of the human agent involved in the call.

`agent_email`

string

No

Email of the agent. Used for permissioning.

`call_time`

timestamp

Yes

ISO 8601 UTC timestamp of when the call occurred.

`persona_id`

string

Yes

Identifier of the Agent QA AI Employee that processed this call.

`agent_mapping_id`

string (UUID)

No

The `agent_qa_mappings` row resolved at ingestion time for this call. Written once on insert and never rewritten. Null for records created before this field was introduced.

`final_score`

float

No

Calculated QA score on a 0–100 scale. Null if evaluation did not complete.

`sentiment`

enum

No

Overall sentiment. One of: `POSITIVE`, `NEGATIVE`, `NEUTRAL`.

`resolution_status`

enum

No

Whether the issue was resolved. One of: `RESOLVED`, `NOT_RESOLVED`.

`contact_reason_category`

list\[string\]

No

Ordered list of categories explaining why the customer contacted support.

`call_driver_reason`

string

No

Detailed free-text reason for the call.

`non_resolution_reason_category`

list\[string\]

No

Reasons the call was not resolved. Only populated when `resolution_status` is `NOT_RESOLVED`.

`agent_talk_time`

float

No

Duration (seconds) the agent was speaking.

`user_talk_time`

float

No

Duration (seconds) the user was speaking.

`dead_air`

float

No

Duration (seconds) of silence/dead air.

`total_audio_time`

float

No

Total audio duration (seconds).

`case_id`

string

No

Ticket/case ID associated with the call.

`agent_tenure`

integer

No

Agent tenure in days.

`rule_results`

list\[object\]

Yes

List of QA rule evaluation results. See below.

`insights`

list\[object\]

Yes

List of behavioural insights. See below.

`transcript`

list\[object\]

Yes

The diarized call transcript as an ordered list of utterances, each `{role, message, start, end}` (`start`/`end` in seconds from call start). A successfully transcribed call with no utterances exports as an empty list, not an error.

#### Insight Attributes

Field

Type

Description

`insight_type`

enum

One of: `STRENGTH`, `WEAKNESS`, `BUG`, `FEEDBACK`, `COMPETITOR`.

`driver`

string

High-level behavioural driver (e.g., "Information verification").

`category`

string

Sub-category within the driver (e.g., "Active listening").

`justification`

string

Explanation for why this insight was surfaced.

`verbatim`

string

Transcript snippet supporting this insight.

#### Rule Result Attributes

Field

Type

Description

`rule_name`

string

Name of the QA rule being evaluated.

`rule_category`

string

Grouping of rules (e.g., "Customer experience", "Call management").

`rule_weight`

float

Weighting of this rule when calculating the final score.

`is_critical`

bool

Whether this is a critical rule. If any critical rule fails, the recording receives a final score of 0.

`rule_mechanism`

enum

Information source used. One of: `TRANSCRIPT`, `TRANSCRIPT_WITH_KNOWLEDGE_BASE`.

`result`

enum

One of: `PASS`, `FAIL`, `NOT_APPLICABLE`.

`rationale`

string

Explanation of why this rule passed, failed, or was not applicable.

`verbatims`

list\[string\]

Transcript snippets supporting the evaluation result.

### Manifest File (`manifest_signed_url` → `manifest.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`.

Field

Type

Description

`tenant_id`

string

The Ema tenant that owns this export.

`persona_id`

string

The AI Employee persona for this export partition.

`date`

string

Calendar date of the export partition in `YYYY-MM-DD` format.

`window_start`

string

ISO 8601 UTC start of the export window (inclusive).

`window_end`

string

ISO 8601 UTC end of the export window (exclusive).

`row_count`

integer

Number of call records written to the data file.

## 14.4 Error Codes

HTTP Code

Reason

`400 Bad Request`

Invalid `persona_id` format or other malformed request parameters.

`401 Unauthorized`

Missing or invalid `Authorization` header.

`403 Forbidden`

Credentials do not have edit-level permission to access the specified persona.

`404 Not Found`

Persona does not exist, or no export is available for the requested date.

`422 Unprocessable Entity`

`date` is not in `YYYY-MM-DD` format, or is today/future (must be a past UTC date).

`400 Bad Request`

The requested `date` is older than your tenant's data retention window; see [2.8 Additional Configuration Tab Settings](/legacy-docs/agent-qa/setup) for retention periods. This applies regardless of whether a partition for that date still physically exists.

`500 Internal Server Error`

Unexpected server-side error.

## 14.5 Rate Limits

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

## 14.6 Sample Python

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

```python
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")
    agent_mapping_id = record.get("agent_mapping_id")
    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']}")
```

## 14.7 On-Demand Current-Day Export

The nightly export (above) always covers a **past, completed** UTC day. To pull data from the **current calendar day** before the next nightly run, request the live export from the Audit tab's export action. It's delivered asynchronously rather than as a direct download, since a busy day's export can take up to a minute to build:

1.  Requesting the export starts a background build and returns a request ID immediately.
2.  The UI polls status until the build completes.
3.  Once complete, a signed download URL is provided, valid for **1 hour**.

> [INFO]
> Today's data is a **partial snapshot** -- calls still being processed won't appear until a later export. This is expected: the current-day export is for freshness, not completeness. Use the nightly export for a fully settled day.
