Customer Value Management · API

Call Quality Assessment API

Submit a call-centre transcript and get an instant quality score, letter grade (A+-E), nine-dimension breakdown with per-rule observations, and sentiment delta powered by a deterministic Service Excellence Index.

Documentation Pricing
POST /v1/ai-agents/call-quality-manager/upload
Auth Bearer token Content-Type multipart/form-data Status Stable · v1

When to use this API

Use this API when you need a consistent, auditable quality score for a customer-care call transcript without building your own scoring pipeline.

The API accepts a plain-text transcript (with speaker labels) or a .txt / .json / .html file, runs it through the nine-dimension Service Excellence Index, and returns a structured result: overall score (0-100), grade letter (A+ to E), per-dimension scores with rule-level observations, call-type classification, and sentiment delta. Language is auto-detected transcripts in Hindi, Tamil, Telugu, Kannada, Malayalam, Marathi, or Punjabi are translated to English for scoring and the original language is preserved in the response.

Authentication

All requests require a user-scoped API key passed as a bearer token. Generate one in the platform under Settings → API Keys. Keys are prefixed ok_ and expire after 90 days.

Authorization header
Authorization: Bearer ok_xxxxxxxxxxxxxxxxxxxxxxxx
Heads-up. API keys are tied to the user account that generated them. Credit deductions are applied to that user's balance. A key that is revoked or expired returns 401 unauthorized.

Parameters

Form fields

NameTypeRequiredDescription
file file required* Transcript file to score. Accepted formats: .txt, .json, .html. HTML files have tags stripped automatically before scoring. Use rawText instead to submit transcript text directly without a file.
rawText string required* Plain-text transcript submitted directly in the form body. Use instead of file when the transcript is already in memory. Speaker labels are expected on each line: AGENT: ... / CUSTOMER: ... (case-insensitive; colon or dash separator).
language string optional language code to override auto-detection. Accepted values: en, hi, ta, te, kn, ml, mr, pa. Omit to let the API detect from character scripts.
categoryId string optional Call category code to override auto-detection. Accepted values: CMPL (Complaint), SALE (Sales), UPSL (Upsell), RETN (Retention), QURY (Service Query), INFO (Information).
categorySubId string optional Sub-category code (e.g. CMPL.BIL for Billing Dispute, SALE.PST for Postpaid New Connection). Supply alongside callTypeId. Omit to let the API infer.

* Exactly one of file or rawText is required per request.

Request

# Score a transcript file
curl -X POST https://platform.openknowra.ai/api/bff/v1/ai-agents/call-quality-manager/upload \
  -H "Authorization: Bearer $OPENKNOWRA_API_KEY" \
  -F "file=@/path/to/transcript.txt"

# Score raw transcript text
curl -X POST https://platform.openknowra.ai/api/bff/v1/ai-agents/call-quality-manager/upload \
  -H "Authorization: Bearer $OPENKNOWRA_API_KEY" \
  -F "rawText=AGENT: Thank you for calling. How can I help you today?
CUSTOMER: Hi, I have an issue with my bill."
import os
import requests

API_KEY = os.environ["OPENKNOWRA_API_KEY"]
API_URL = "https://platform.openknowra.ai/api/bff/v1/ai-agents/call-quality-manager/upload"

def score_transcript(file_path: str) -> dict:
    with open(file_path, "rb") as f:
        res = requests.post(
            API_URL,
            headers={"Authorization": f"Bearer {API_KEY}"},
            files={"file": f},
        )
    if res.status_code == 402:
        err = res.json()["error"]
        raise ValueError(
            f"Insufficient credits: need {err['required']}, have {err['balance']}"
        )
    res.raise_for_status()
    return res.json()

result = score_transcript("./call_transcript.txt")
assessment = result["assessment"]
print(f"Grade: {assessment['grade']}  Score: {assessment['overallScore']}/100")
for factor in assessment.get("factors", []):
    print(f"  {factor['label']}: {factor['score']}/{factor['maxScore']}")
import fs from "fs";

const API_KEY = process.env.OPENKNOWRA_API_KEY!;
const API_URL = "https://platform.openknowra.ai/api/bff/v1/ai-agents/call-quality-manager/upload";

async function scoreTranscript(filePath: string) {
  const form = new FormData();
  const buffer = fs.readFileSync(filePath);
  form.append("file", new Blob([buffer]), "transcript.txt");

  const res = await fetch(API_URL, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form,
  });

  if (res.status === 402) {
    const err = await res.json() as { error: { required: number; balance: number } };
    throw new Error(
      `Insufficient credits: need ${err.error.required}, have ${err.error.balance}`
    );
  }

  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

const result = await scoreTranscript("./call_transcript.txt");
const { grade, overallScore, factors } = result.assessment;
console.log(`Grade: ${grade}  Score: ${overallScore}/100`);
console.log(JSON.stringify(factors, null, 2));

Response

200 OK Returns the scored call with overall grade, nine-dimension factor breakdown, call metadata, and sentiment delta.

application/json
{
  "callId": "cq_7f3a9b2c",
  "idempotent": false,
  "assessment": {
    "overallScore": 79,
    "grade": "B",
    "decision": "meets_standard",
    "coachingPriority": "low",
    "language": "en",
    "callType": { "id": "CMPL", "label": "Complaint" },
    "subCategory": { "id": "CMPL.BIL", "label": "Billing Dispute" },
    "turnCount": 18,
    "agentTurnCount": 9,
    "customerTurnCount": 9,
    "talkToListenRatio": 48,
    "talkToListenBand": "balanced",
    "customerSentimentLabel": "positive",
    "sentimentTrend": "improving",
    "outcomeLabel": "resolved",
    "factors": [
      { "label": "Empathy & Tone",          "score": 10, "maxScore": 12 },
      { "label": "First Call Resolution",   "score": 11, "maxScore": 14 },
      { "label": "Compliance Checklist",    "score": 10, "maxScore": 12 },
      { "label": "Active Listening",        "score":  8, "maxScore": 10 },
      { "label": "Product / Plan Accuracy", "score":  9, "maxScore": 12 },
      { "label": "Solution Orientation",    "score":  8, "maxScore": 10 },
      { "label": "Brand / Upsell",          "score":  7, "maxScore": 10 },
      { "label": "Conversation Closure",    "score":  9, "maxScore": 10 },
      { "label": "Sentiment Delta",         "score":  7, "maxScore": 10 }
    ]
  },
  "newBalance": 875
}

Grade bands

GradeScore rangeDecisionCoaching Priority
A+≥ 95Excellence demonstrated Skip Coaching
A 85-94Excellence Skip Coaching
B 70-84Meets Standard Scheduled Coaching
C 50-69Coaching Recommended Prompt Coaching
D 30-49Below Standard Urgent Coaching
E 0-29Urgent Review Escalate Immediately

Nine scoring dimensions

DimensionMax ptsWhat it measures
Empathy & Tone 12Emotional attunement, warmth, and professional tone throughout the call.
First Call Resolution 14Issue resolved in one call with no callback or follow-up needed.
Compliance Checklist 12Adherence to regulatory scripts, mandatory disclosures, and identity verification.
Active Listening 10Acknowledgement, paraphrasing, and confirming customer understanding.
Product / Plan Accuracy 12Correct and complete product, pricing, and plan information provided; penalises vague terms.
Solution Orientation 10Proactive effort to resolve the issue rather than deflect or escalate.
Brand / Upsell 10Brand representation quality and contextually relevant upsell identification.
Conversation Closure 10Quality of call wrap-up: summary, confirmation, warm sign-off.
Sentiment Delta 10Change in customer emotional tone from call open to call close.

Dashboard KPIs

Every scored response includes derived analytics fields alongside the factors array. These do not affect overallScore they are surface labels computed from the transcript for client-facing dashboards.

FieldTypeDescription
turnCount integerTotal number of speaker turns in the transcript.
agentTurnCount integerTurns attributed to the agent.
customerTurnCount integerTurns attributed to the customer.
talkToListenRatio integerPercentage of total spoken words contributed by the agent (0-100). Healthy band: 30-50.
talkToListenBand string One of under_engaged (<30 %), balanced (30-49 %), agent_led (50-64 %), agent_dominates (≥65 %).
customerSentimentLabelstringCustomer mood at call close: positive, neutral, or negative.
sentimentTrend string Direction of mood change: improving, stable, or worsening.
outcomeLabel string Conversation outcome: resolved, partial, unresolved, or transferred.

Audio Transcription (optional)

When you have a call recording instead of a ready-made transcript, use the two-step audio pipeline. The API uploads the file to AWS Transcribe, performs speaker diarization, and returns a AGENT: / CUSTOMER: transcript you can pass straight to the upload endpoint.

Note. These endpoints require AWS Transcribe + S3 credentials configured on the platform. If not provisioned, both return 503 Service Unavailable.

Step 1 : Upload audio

POST /v1/ai-agents/call-quality-manager/transcribe-upload

Send as multipart/form-data. The uploaded file is pushed straight to S3 and a Transcribe job is started.

FieldTypeRequiredDescription
audio file required The audio file. Accepted formats: mp3, wav, m4a, webm, ogg, flac. Maximum size 30 MB.
lang string optional AWS Transcribe language code, e.g. en-US, hi-IN, ta-IN. Defaults to en-US.
curl -X POST https://platform.openknowra.ai/api/bff/v1/ai-agents/call-quality-manager/transcribe-upload \
  -H "Authorization: Bearer $OPENKNOWRA_API_KEY" \
  -F "audio=@call_recording.mp3"
200 OK job started
{
  "jobName":     "se-call-1719388800000-a1b2c3d4",
  "s3Key":       "se-transcribe/inbox/1719388800000-a1b2c3d4.mp3",
  "mediaFormat": "mp3",
  "languageCode":"en-US",
  "status":      "IN_PROGRESS",
  "message":     "Transcription started · poll GET /transcribe-job/se-call-..."
}

Step 2 : Poll job status

GET /v1/ai-agents/call-quality-manager/transcribe-job/{jobName}
Query paramValuesDescription
speakerMode string agentFirst (default) first detected speaker mapped to Agent. customerFirst first speaker mapped to Customer. Swap if the diarization assigns the wrong role.
200 OK completed
{
  "status":     "COMPLETED",
  "language":   "en-US",
  "transcript": "AGENT: Thank you for calling. How can I help?\nCUSTOMER: Hi, I have an issue with my bill.",
  "turns": [
    { "speaker": "Agent",    "startMs": 0,    "endMs": 2400, "text": "Thank you for calling. How can I help?" },
    { "speaker": "Customer", "startMs": 2800, "endMs": 5100, "text": "Hi, I have an issue with my bill." }
  ]
}

When status is IN_PROGRESS, the response contains only { "status": "IN_PROGRESS", "language": "..." } poll again after a few seconds. Transcription typically completes in 30-90 seconds for a 10-minute call. Pass the returned transcript string as the rawText field in a subsequent upload request.

Bulk scoring

Score up to 100 transcripts in a single request instead of calling /upload once per transcript.

POST /v1/ai-agents/call-quality-manager/bulk-upload
FieldTypeRequiredDescription
transcripts array required Array of items, up to 100 per request (25 MB total request body). Each item is either a plain transcript string, or an object { "transcript": "...", "categoryId"?, "subCategoryId"?, "lang"? } carrying its own per-item overrides explicit metadata on an item always wins over any top-level default, and is otherwise auto-detected from that item's text exactly as a single /upload call would.
categoryId / subCategoryId / lang string optional Top-level defaults applied to every item that doesn't specify its own override.
Credits. The full batch cost is deducted upfront (CREDITS_PER_UPLOAD × transcripts.length) and refunded per item for anything that comes back ok: false or unscorable: true junk, one-sided, or too-short input (e.g. a transcript under ~12 words) returns unscorable: true with a reason instead of a fabricated score, and is never charged.
# transcripts is an array of strings and/or objects up to 100 per request
curl -X POST https://platform.openknowra.ai/api/bff/v1/ai-agents/call-quality-manager/bulk-upload \
  -H "Authorization: Bearer $OPENKNOWRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transcripts": [
      "AGENT: Thank you for calling...\nCUSTOMER: Hi, I am calling about...",
      { "transcript": "AGENT: Hello!\nCUSTOMER: I want to upgrade my plan...", "categoryId": "SALE" }
    ]
  }'
200 OK
{
  "total": 2,
  "succeeded": 1,
  "failed": 1,
  "results": [
    { "index": 0, "ok": true, "callId": "cq_9d21ab77", "assessment": { "overallScore": 74, "grade": "B", "decision": "meets_standard", "coachingPriority": "low", "factors": [ /* ... */ ] } },
    { "index": 1, "ok": true, "unscorable": true, "reason": "This transcript is not a scorable agent-customer conversation (too short, or missing a real two-sided exchange). Provide a fuller call with AGENT: and CUSTOMER: turns." }
  ],
  "newBalance": 850
}

The platform's own Bulk Upload UI additionally accepts .json files (a single object, or an array of items in the same shape above) and .zip files (bundling multiple .txt/.html/.json entries, including sub-folders one transcript per file, or many if a .json entry holds an array); the same 100 transcripts / 25 MB limit applies whether you use that UI or call the API directly as shown above.

Errors

StatusCodeMeaning
400-No transcript provided, or transcript is empty after parsing.
401unauthorizedMissing, invalid, expired, or revoked API key.
402insufficient_creditsAccount balance too low. Response includes required (credits needed) and balance (current balance).
422-Transcript could not be parsed no valid speaker-labelled turns found.
500-Scoring engine error. Safe to retry after a short delay.
402 - insufficient_credits
{
  "error": {
    "code":     "insufficient_credits",
    "required": 25,
    "balance":  10
  }
}