CSAT Ingestion API
The CSAT Ingestion API uploads a customer satisfaction score or free-text feedback for a call that has already been submitted to Agent QA via the Audio Ingestion API or Transcript Ingestion API. Ema uses the CSAT signal to enrich the call's QA evaluation.
The resource_id and agent_id in the CSAT payload must exactly match the values used during the original upload — they are how Ema joins the CSAT to the right call.
14.1 Authentication & Headers
| Header | Type | Required | Description |
|---|---|---|---|
Authorization | string | Yes | Bearer token (e.g., Bearer <token>) |
Content-Type | string | Yes | application/json |
Note — unlike the Audio and Transcript Ingestion APIs, CSAT Ingestion does not use the x-persona-id header. The Agent QA persona is passed as persona_id in the JSON body.
14.2 Upload CSAT
| Property | Value |
|---|---|
| URL | {base_url}/api/agent-qa/upload/csat |
| HTTP Method | POST |
| Content-Type | application/json |
Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
resource_id | string | Yes | External identifier for the call. Must match the resource_id used when uploading the voice file or transcript. |
agent_id | string | Yes | Identifier of the human agent associated with the call. Must match the agent_id used when uploading the voice file or transcript. |
csat_input | string | Yes | Either a numeric score ("1" through "5") or free-text feedback. If parseable as an integer 1–5, it is treated as a numeric score; otherwise it is treated as text feedback. The value must be sent as a string. |
persona_id | string | Yes | The unique identifier of the Agent QA AI Employee (persona). |
Example Request
curl -X POST \
"{base_url}/api/agent-qa/upload/csat" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"resource_id": "resource_id_123",
"agent_id": "human_agent_001",
"persona_id": "<persona_id>",
"csat_input": "4"
}'
Success Response (200 OK)
The response indicates whether CSAT evaluation was triggered immediately or deferred. If the Agent QA workflow for the call has already finished, evaluation is triggered right away:
{
"status": "ok",
"message": "CSAT uploaded and evaluation triggered",
"csat_triggered": true
}
If the Agent QA workflow has not yet completed, the CSAT is stored and evaluation is automatically triggered once the workflow finishes. You do not need to re-upload the CSAT in this case:
{
"status": "ok",
"message": "CSAT uploaded, evaluation will be triggered after call analysis completes",
"csat_triggered": false
}
14.3 Error Codes
| HTTP Code | Reason |
|---|---|
400 Bad Request | Missing required fields, invalid UUID format, or no matching call found for the provided resource_id and agent_id. |
401 Unauthorized | Missing or invalid Authorization header. |
403 Forbidden | Credentials do not have permission to access the specified persona. |
429 Too Many Requests | Rate limit exceeded. Retry with exponential backoff. |
500 Internal Server Error | Unexpected server-side error. |
14.4 Sample Python
import requests
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}",
"Content-Type": "application/json",
}
def upload_csat(resource_id, agent_id, persona_id, csat_input):
"""Uploads a CSAT score or free-text feedback for a previously ingested call.
Args:
resource_id: Must match the resource_id used when uploading the voice file.
agent_id: Must match the agent_id used when uploading the voice file.
persona_id: The Agent QA AI Employee (persona) ID.
csat_input: A string — either a numeric score ("1"–"5") or free-text feedback.
Values outside 1–5 are treated as free-text.
"""
url = f"{BASE_URL}/api/agent-qa/upload/csat"
payload = {
"resource_id": resource_id,
"agent_id": agent_id,
"persona_id": persona_id,
"csat_input": csat_input,
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
result = response.json()
print(f"Success! csat_triggered: {result.get('csat_triggered')}")
return result
print(f"Failed! Status: {response.status_code}")
print(f"Error: {response.text}")
return None
if __name__ == "__main__":
# Example 1 — CSAT as a numeric score ("1"–"5" as a string).
upload_csat(
resource_id="resource_id_123",
agent_id="human_agent_001",
persona_id=AI_EMPLOYEE_ID,
csat_input="4",
)
# Example 2 — CSAT as free-text feedback.
upload_csat(
resource_id="resource_id_456",
agent_id="human_agent_002",
persona_id=AI_EMPLOYEE_ID,
csat_input="The agent was very helpful and resolved my issue quickly.",
)