Chanl

Evaluate Quality

Score calls with custom criteria and track compliance with scorecards

Scorecards let you define what "good" looks like, then grade every call against those standards automatically. The workflow is: create a scorecard with weighted criteria, evaluate a call, and read the detailed breakdown.

Installation

npm install @chanl/sdk

Setup

import { Chanl } from '@chanl/sdk';

const chanl = new Chanl({
  apiKey: process.env.CHANL_API_KEY,
});

Create a scorecard

A scorecard is a grading rubric. It has categories (weighted sections) and criteria (individual checks).

const scorecard = await chanl.scorecard.create({
  name: 'Customer Service Quality',
  description: 'Evaluates customer service call quality',
  status: 'active',
  passingThreshold: 75,
});

console.log('Created scorecard:', scorecard.id);

Parameters:

ParameterTypeRequiredDescription
namestringYesScorecard name
descriptionstringNoWhat this scorecard evaluates
statusstringNoactive or inactive
passingThresholdnumberNoMinimum score to pass (0-100)

Returns: Promise<Scorecard>

Add categories

Categories group related criteria and carry a weight that determines their share of the total score.

const communication = await chanl.scorecard.createCategory(scorecard.id, {
  name: 'Communication',
  description: 'Agent communication skills',
  weight: 40, // 40% of total score
});

const resolution = await chanl.scorecard.createCategory(scorecard.id, {
  name: 'Resolution',
  description: 'Problem solving effectiveness',
  weight: 35,
});

const compliance = await chanl.scorecard.createCategory(scorecard.id, {
  name: 'Compliance',
  description: 'Regulatory requirements',
  weight: 25,
});

Parameters:

ParameterTypeRequiredDescription
namestringYesCategory name
descriptionstringNoWhat this category measures
weightnumberYesPercentage weight (all categories should sum to 100)

Add criteria

Each criterion is one thing you are checking for. Chanl supports several evaluation types.

Prompt (AI-evaluated)

The LLM reads the transcript and judges whether the criterion was met. Best for subjective qualities like empathy, clarity, and professionalism.

await chanl.scorecard.createCriteria(scorecard.id, {
  categoryId: communication.id,
  key: 'greeting',
  name: 'Professional Greeting',
  type: 'prompt',
  settings: {
    description: 'Agent greeted the customer professionally with name and company',
  },
});

await chanl.scorecard.createCriteria(scorecard.id, {
  categoryId: communication.id,
  key: 'active_listening',
  name: 'Active Listening',
  type: 'prompt',
  settings: {
    description: 'Agent demonstrated active listening by acknowledging customer concerns',
  },
});

Keyword (pattern matching)

Checks whether specific words or phrases appear (or do not appear) in the transcript. Best for compliance requirements.

await chanl.scorecard.createCriteria(scorecard.id, {
  categoryId: compliance.id,
  key: 'recording_disclosure',
  name: 'Recording Disclosure',
  type: 'keyword',
  settings: {
    matchType: 'must_contain',
    keywords: ['this call may be recorded', 'call is being recorded'],
  },
});

Response time

Evaluates how quickly the agent responded.

await chanl.scorecard.createCriteria(scorecard.id, {
  categoryId: resolution.id,
  key: 'response_speed',
  name: 'Quick Response',
  type: 'response_time',
  settings: {
    maxMs: 3000, // Maximum 3 seconds
  },
});

Other criteria types

TypeWhat it checksSettings
talk_timeAgent/customer speaking ratiominAgentPercent, maxAgentPercent
silenceDead air duration and frequencymaxSilenceMs, maxSilenceCount
interruptionsSpeaking over the customermaxCount

Criteria parameters:

ParameterTypeRequiredDescription
categoryIdstringYesParent category ID
keystringYesUnique key within the scorecard
namestringYesDisplay name
typestringYesprompt, keyword, response_time, talk_time, silence, interruptions
settingsobjectYesType-specific configuration

Evaluate a call

Run a scorecard against a call transcript. The call must have finished processing (status ended).

const result = await chanl.scorecard.evaluate(
  callId,
  { scorecardId: scorecard.id }
);

console.log('Score:', result.score + '%');
console.log('Passed:', result.passed ? 'Yes' : 'No');
console.log('Category Scores:', result.categoryScores);

Parameters:

ParameterTypeRequiredDescription
callIdstringYesThe call to evaluate
scorecardIdstringYesThe scorecard to grade against
forcebooleanNoRe-evaluate even if already scored

Returns: Promise<{ score: number; passed: boolean; status: string; categoryScores: Record<string, number> }>

Get detailed results

The results include per-criteria scores, evidence from the transcript, and improvement suggestions.

const { results } = await chanl.scorecard.getResults(callId);

results.forEach((result) => {
  console.log(`\n${result.scorecardName}: ${result.score}%`);

  result.criteriaResults.forEach((cr) => {
    const status = cr.passed ? 'PASS' : 'FAIL';
    console.log(`  ${status} ${cr.name}: ${cr.score}%`);
    if (cr.evidence) {
      console.log(`    Evidence: "${cr.evidence}"`);
    }
    if (cr.suggestion) {
      console.log(`    Suggestion: ${cr.suggestion}`);
    }
  });
});

Parameters:

ParameterTypeRequiredDescription
callIdstringYesThe call to get results for
scorecardIdstringNoFilter to a specific scorecard

Returns: Promise<{ results: ScorecardResult[] }>

Evaluate on import

Skip a step by evaluating calls at import time. Pass a scorecardId and results are ready as soon as processing completes.

const result = await chanl.calls.import({
  transcript: '...',
  analysisFields: ['sentiment', 'quality'],
  scorecardId: 'scorecard_abc123',
});

// Results available after processing
const { results } = await chanl.scorecard.getResults(result.callId);

Batch evaluation

Evaluate a set of calls against the same scorecard and get aggregate statistics.

async function evaluateBatch(callIds: string[], scorecardId: string) {
  const results = await Promise.all(
    callIds.map((callId) =>
      chanl.scorecard.evaluate(callId, { scorecardId })
    )
  );

  const passed = results.filter((r) => r.passed).length;
  const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;

  console.log(`Evaluated ${results.length} calls`);
  console.log(`Passed: ${passed} (${((passed / results.length) * 100).toFixed(1)}%)`);
  console.log(`Average Score: ${avgScore.toFixed(1)}%`);

  return results;
}

Full example

Build a comprehensive customer service scorecard and evaluate a call against it.

import { Chanl } from '@chanl/sdk';

const chanl = new Chanl({
  apiKey: process.env.CHANL_API_KEY,
});

async function createQualityScorecard() {
  const scorecard = await chanl.scorecard.create({
    name: 'Customer Service Excellence',
    description: 'Comprehensive evaluation of customer service quality',
    status: 'active',
    passingThreshold: 80,
  });

  // Opening (20%)
  const opening = await chanl.scorecard.createCategory(scorecard.id, {
    name: 'Opening', weight: 20,
  });
  await chanl.scorecard.createCriteria(scorecard.id, {
    categoryId: opening.id,
    key: 'greeting',
    name: 'Professional Greeting',
    type: 'prompt',
    settings: { description: 'Agent introduced themselves with name and company name' },
  });
  await chanl.scorecard.createCriteria(scorecard.id, {
    categoryId: opening.id,
    key: 'recording',
    name: 'Recording Disclosure',
    type: 'keyword',
    settings: { matchType: 'must_contain', keywords: ['call may be recorded'] },
  });

  // Communication (30%)
  const communication = await chanl.scorecard.createCategory(scorecard.id, {
    name: 'Communication', weight: 30,
  });
  await chanl.scorecard.createCriteria(scorecard.id, {
    categoryId: communication.id,
    key: 'clarity',
    name: 'Clear Explanation',
    type: 'prompt',
    settings: { description: 'Agent explained information clearly without jargon' },
  });
  await chanl.scorecard.createCriteria(scorecard.id, {
    categoryId: communication.id,
    key: 'no_interruptions',
    name: 'No Interruptions',
    type: 'interruptions',
    settings: { maxCount: 2 },
  });

  // Resolution (35%)
  const resolution = await chanl.scorecard.createCategory(scorecard.id, {
    name: 'Resolution', weight: 35,
  });
  await chanl.scorecard.createCriteria(scorecard.id, {
    categoryId: resolution.id,
    key: 'solution',
    name: 'Solution Provided',
    type: 'prompt',
    settings: { description: 'Agent provided a clear solution or next steps' },
  });

  // Closing (15%)
  const closing = await chanl.scorecard.createCategory(scorecard.id, {
    name: 'Closing', weight: 15,
  });
  await chanl.scorecard.createCriteria(scorecard.id, {
    categoryId: closing.id,
    key: 'additional_help',
    name: 'Offered Additional Help',
    type: 'prompt',
    settings: { description: 'Agent asked if there was anything else they could help with' },
  });

  return scorecard;
}

// Create the scorecard, then evaluate a call
const scorecard = await createQualityScorecard();
const evaluation = await chanl.scorecard.evaluate(callId, {
  scorecardId: scorecard.id,
});

console.log(`Score: ${evaluation.score}% - ${evaluation.passed ? 'PASSED' : 'FAILED'}`);

Next steps

On this page