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

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token (e.g., Bearer <token>)
Content-TypestringYesapplication/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

PropertyValue
URL{base_url}/api/agent-qa/upload/csat
HTTP MethodPOST
Content-Typeapplication/json

Body Fields

FieldTypeRequiredDescription
resource_idstringYesExternal identifier for the call. Must match the resource_id used when uploading the voice file or transcript.
agent_idstringYesIdentifier of the human agent associated with the call. Must match the agent_id used when uploading the voice file or transcript.
csat_inputstringYesEither 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_idstringYesThe 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 CodeReason
400 Bad RequestMissing required fields, invalid UUID format, or no matching call found for the provided resource_id and agent_id.
401 UnauthorizedMissing or invalid Authorization header.
403 ForbiddenCredentials do not have permission to access the specified persona.
429 Too Many RequestsRate limit exceeded. Retry with exponential backoff.
500 Internal Server ErrorUnexpected 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.",
    )

Last updated: Jul 3, 2026