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. Either run the AI audit automatically (needs the relay + Anthropic API key set up above), or copy a ready-made prompt to paste into claude.ai yourself — that path needs no API key at all. Either way, every result is editable and you can fill in everything manually too.

Paste Claude's response

After Claude replies in the claude.ai chat, paste its full reply here (raw JSON, or JSON inside a code block — either works) and click Apply. This is only needed if you used "Copy prompt for claude.ai" above instead of the automatic button.

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