Call Quality Audit

Paste a call transcript and this scores it automatically against the referral-outreach rubric — critical guardrails, core execution, and professionalism — using the same tiered logic as the master scorecard.

Awaiting audit

Call details

Basic identifiers for the record.

Staff Name
Date
Call ID

Connections

Pull a call straight from Aircall, and send finished audits to a Google Sheet. Both go through one small relay script you deploy once — see setup below.

⚙ One-time setup — Google Sheet + AI scoring + Aircall relay
  1. Go to script.google.comNew project (this script is already hardcoded to write into the audit spreadsheet by ID/tab, so it doesn't need to be opened from inside that sheet).
  2. Delete the placeholder code and paste in the script below.
  3. In the Apps Script editor: Project Settings → Script Properties, add ANTHROPIC_API_KEY (from console.anthropic.com → API Keys) — this is required for the "Run AI audit" button to work at all; it proxies the scoring call through this script so the key is never exposed in the page. Also add AIRCALL_API_ID and AIRCALL_API_TOKEN (from Aircall Dashboard → Company Settings → API Keys) if you want the Aircall import button too — that part is optional.
  4. Deploy → New deployment → Web app. Execute as Me, Access Anyone. The first deploy will prompt a Google OAuth consent screen asking for Sheets access — approve it (this is your own script, running as you). Copy the resulting URL into the field above.
function doPost(e) {
  var body = JSON.parse(e.postData.contents);
  if (body.action === 'submit') return handleSubmit(body.payload);
  if (body.action === 'fetchCall') return handleFetchCall(body.callId);
  if (body.action === 'auditCall') return handleAuditCall(body.payload);
  return json_({error: 'Unknown action'});
}

function doGet(e) {
  return json_({status: 'Audit relay is running'});
}

var TARGET_SPREADSHEET_ID = '1y_iwRNLomBeeXM9g6nXzIJwfq-X_xMhrsUQnTjHTjCE';
var TARGET_GID = 2132488623;

function getTargetSheet_() {
  var ss = SpreadsheetApp.openById(TARGET_SPREADSHEET_ID);
  var sheets = ss.getSheets();
  for (var i = 0; i < sheets.length; i++) {
    if (sheets[i].getSheetId() === TARGET_GID) return sheets[i];
  }
  throw new Error('Tab with gid ' + TARGET_GID + ' not found in the target spreadsheet.');
}

function handleSubmit(payload) {
  var sh = getTargetSheet_();
  if (sh.getLastRow() === 0) {
    sh.appendRow(['Timestamp','Staff Name','Date','Call ID','Identity','Safety','Consent',
      'Intro','Program','Insurance','Scheduling','Closing','Objection','Tone',
      'SLA','Canvas','Consent Sent','Total Score %','Tier','Needs Review','Note']);
  }
  sh.appendRow([new Date(), payload.staffName, payload.date, payload.callId,
    payload.identity, payload.safety, payload.consent, payload.intro, payload.program,
    payload.insurance, payload.scheduling, payload.closing, payload.objection, payload.tone,
    payload.sla, payload.canvas, payload.consent_sent, payload.total, payload.tier, payload.needsReview,
    payload.note]);
  return json_({status: 'ok'});
}

function handleFetchCall(callId) {
  var props = PropertiesService.getScriptProperties();
  var apiId = props.getProperty('AIRCALL_API_ID');
  var apiToken = props.getProperty('AIRCALL_API_TOKEN');
  var auth = Utilities.base64Encode(apiId + ':' + apiToken);
  var opts = { headers: { Authorization: 'Basic ' + auth }, muteHttpExceptions: true };

  var callRes = UrlFetchApp.fetch('https://api.aircall.io/v1/calls/' + callId, opts);
  var call = JSON.parse(callRes.getContentText());
  if (call.error) return json_({error: call.error, troubleshoot: call.troubleshoot});

  var transcriptText = '';
  try {
    var trRes = UrlFetchApp.fetch('https://api.aircall.io/v1/calls/' + callId + '/transcription', opts);
    var tr = JSON.parse(trRes.getContentText());
    if (tr.transcription && tr.transcription.utterances) {
      transcriptText = tr.transcription.utterances.map(function(u) {
        return (u.participant && u.participant.name ? u.participant.name : 'Speaker') + ': ' + u.text;
      }).join('\\n');
    }
  } catch (err) {
    transcriptText = '';
  }

  return json_({
    callId: callId,
    staffName: call.call && call.call.user ? call.call.user.name : '',
    date: call.call ? new Date(call.call.started_at * 1000).toISOString().slice(0,10) : '',
    recording: call.call ? call.call.recording : '',
    transcript: transcriptText
  });
}

function handleAuditCall(payload) {
  var props = PropertiesService.getScriptProperties();
  var apiKey = props.getProperty('ANTHROPIC_API_KEY');
  if (!apiKey) return json_({error: 'ANTHROPIC_API_KEY is not set in Script Properties.'});

  var opts = {
    method: 'post',
    contentType: 'application/json',
    headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
    payload: JSON.stringify({
      model: 'claude-sonnet-4-6',
      max_tokens: 1800,
      system: payload.system,
      messages: [{ role: 'user', content: payload.userMessage }]
    }),
    muteHttpExceptions: true
  };

  var res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', opts);
  var data = JSON.parse(res.getContentText());
  if (data.error) return json_({error: data.error.message || 'Anthropic API error'});

  var text = (data.content || []).filter(function(b){ return b.type === 'text'; })
    .map(function(b){ return b.text; }).join('\n');
  return json_({text: text});
}

function json_(obj) {
  return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);
}

Transcript import needs Aircall's AI Assist add-on enabled on your account. Without it, "Import from Aircall" still pulls staff name, date, and the recording link — paste the transcript in manually alongside it.

Transcript

Paste the full call transcript below, then run the audit — the AI will determine whether any critical guardrail failed, and will suggest a point score for every Core Execution / Situational / Professionalism item. You can also fill in everything manually without running the AI at all, and you can override any AI-suggested number.

Tiered scoring framework

Tiered Scoring Framework Quality Metrics Scores
Green Zone (≥ 90%) → Excellent quality call Critical Guardrail
Yellow Zone (85–89%) → Acceptable but improvable Core Execution
Red Zone (< 85%) → Needs coaching or corrective action Process/Compliance
Total Score

Note