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.
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: Bearer ok_xxxxxxxxxxxxxxxxxxxxxxxx
401 unauthorized.
Parameters
Form fields
| Name | Type | Required | Description |
|---|---|---|---|
| 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.
{
"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
| Grade | Score range | Decision | Coaching Priority |
|---|---|---|---|
| A+ | ≥ 95 | Excellence demonstrated | Skip Coaching |
| A | 85-94 | Excellence | Skip Coaching |
| B | 70-84 | Meets Standard | Scheduled Coaching |
| C | 50-69 | Coaching Recommended | Prompt Coaching |
| D | 30-49 | Below Standard | Urgent Coaching |
| E | 0-29 | Urgent Review | Escalate Immediately |
Nine scoring dimensions
| Dimension | Max pts | What it measures |
|---|---|---|
| Empathy & Tone | 12 | Emotional attunement, warmth, and professional tone throughout the call. |
| First Call Resolution | 14 | Issue resolved in one call with no callback or follow-up needed. |
| Compliance Checklist | 12 | Adherence to regulatory scripts, mandatory disclosures, and identity verification. |
| Active Listening | 10 | Acknowledgement, paraphrasing, and confirming customer understanding. |
| Product / Plan Accuracy | 12 | Correct and complete product, pricing, and plan information provided; penalises vague terms. |
| Solution Orientation | 10 | Proactive effort to resolve the issue rather than deflect or escalate. |
| Brand / Upsell | 10 | Brand representation quality and contextually relevant upsell identification. |
| Conversation Closure | 10 | Quality of call wrap-up: summary, confirmation, warm sign-off. |
| Sentiment Delta | 10 | Change 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.
| Field | Type | Description |
|---|---|---|
| turnCount | integer | Total number of speaker turns in the transcript. |
| agentTurnCount | integer | Turns attributed to the agent. |
| customerTurnCount | integer | Turns attributed to the customer. |
| talkToListenRatio | integer | Percentage 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 %).
|
| customerSentimentLabel | string | Customer 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.
503 Service
Unavailable.
Step 1 : Upload audio
Send as multipart/form-data. The uploaded file is pushed straight to
S3 and a Transcribe job is started.
| Field | Type | Required | Description |
|---|---|---|---|
| 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"{
"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
| Query param | Values | Description |
|---|---|---|
| speakerMode | string |
agentFirst (default) first detected speaker mapped to Agent.
customerFirst first speaker mapped to Customer.
Swap if the diarization assigns the wrong role.
|
{
"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.
| Field | Type | Required | Description |
|---|---|---|---|
| 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_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" }
]
}'{
"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
| Status | Code | Meaning |
|---|---|---|
| 400 | - | No transcript provided, or transcript is empty after parsing. |
| 401 | unauthorized | Missing, invalid, expired, or revoked API key. |
| 402 | insufficient_credits | Account 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. |
{
"error": {
"code": "insufficient_credits",
"required": 25,
"balance": 10
}
}