# Call Score: AI Call Review & QA for Receptionists and Sales (`nerolabs/call-score`) Actor

Scores call recordings from a dataset, CSV or Google Sheet, keeping your columns: outcome, booked or not, lead quality, missed questions, 0 to 10 score and a speaker transcript. Inputs: recording links, businessContext, customQuestions. Charged per call minute. Agent-ready: x402, MCP.

- **URL**: https://apify.com/nerolabs/call-score.md
- **Developed by:** [Adam Pearce](https://apify.com/nerolabs) (community)
- **Categories:** AI, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $28.00 / 1,000 call minute revieweds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Call Score: AI Call Review & QA for Receptionists and Sales Calls

Nobody listens to every call. This Actor does. Give it your call recordings (from a spreadsheet, a CSV export, a Google Sheet or an Apify dataset) and for every call it tells you:

- **Did we win the job?** Outcome (booked, callback arranged, details taken, caller lost...) and a plain `booked` yes or no.
- **Was it worth having?** Call type, lead quality (hot, warm, cold, not a lead) and urgency.
- **What went wrong?** Questions the caller asked that never got answered, objections, and the specific things the receptionist should have done.
- **How well was it handled?** A 0 to 10 score with the reasons behind it.
- **What now?** The one next step to take, plus the caller's name, number and postcode when they were said on the call.
- **Proof.** A full transcript with speakers, so any score can be checked against what was actually said.

Your own columns (call ID, date, client, agent) stay on every row, so the results drop straight back into your CRM or report.

### Who it is for

- **Agencies running AI receptionists and voice agents** (GoHighLevel Voice AI, Vapi, Retell, Bland, Synthflow): show each client, every month, how many calls were booked, which ones were lost and why. It turns "trust us, the AI works" into a report.
- **Trades and service businesses**: find the calls your front desk or answering service lost before the customer rang a competitor.
- **Sales teams**: score discovery and booking calls against your own playbook with custom questions.
- **AI agents and automations**: an agent can pull last week's recordings, score them and post the lost leads to Slack, with no human listening.

Only need the words? Use the sister Actor **[Bulk Transcription](https://apify.com/nerolabs/bulk-transcription)**.

### Input

| Input | What to put in |
|---|---|
| **Dataset**, **File or Google Sheet URL**, or **Call recording URLs** | Where the recording links are. CSV, Excel, JSON, a Google Sheets link shared as "Anyone with the link can view", an Apify dataset, or a plain list. The link column is found automatically. |
| **About the business** | A few lines on what a good call looks like for you: the trade, area, whether prices are given on the phone, and what the receptionist should do (book a visit, take name, number and postcode, flag emergencies). Every call is judged against this. |
| **Custom questions** | Up to 10 extra questions answered for every call, for example "Did the receptionist mention the free quote?" or "Which competitor did the caller mention?". No extra charge. |

Recordings can be MP3, WAV, M4A, MP4, WebM, OGG, FLAC and more; Google Drive and Dropbox share links work. A recording link must open the file directly (most phone systems export these, or give a link per call in their call log export).

### Output

One row per call. Example, shortened:

```json
{
    "callId": "C-1002",
    "client": "Demo Plumbing and Heating",
    "callStatus": "ok",
    "handlingScore": 3,
    "outcome": "caller_lost",
    "booked": false,
    "callType": "new_enquiry",
    "leadQuality": "warm",
    "urgency": "soon",
    "serviceRequested": "Quote for a new combi boiler, fitted",
    "summary": "A caller wanted a quote and a visit for a new combi boiler. The business could not give a price or finance details, would not book a visit or promise a callback, and the caller said they would try someone else.",
    "unansweredQuestions": ["Roughly how much would a new combi boiler cost fitted?", "Do you offer finance?"],
    "missedOpportunities": ["Take the caller's phone number and postcode", "Offer a visit on Thursday or Friday", "Give a rough price range"],
    "callerSentiment": "negative",
    "scoreReasons": ["Could not answer basic questions", "No visit booked and no callback promised", "No contact details taken"],
    "followUpAction": "Take the caller's details if they ring again, offer a survey this week and a callback with a price range.",
    "customAnswers": [{ "question": "Was a callback time promised?", "answer": "No" }],
    "durationMinutes": 0.75,
    "minutesCharged": 1,
    "transcript": "Speaker A: Good afternoon, Demo Plumbing and Heating, how can I help?\nSpeaker B: Hi, I'm after a quote for a new combi boiler..."
}
```

The run's **OUTPUT** record gives the totals for a report: calls scored, average score, calls booked, booking rate and a count of each outcome. Add **Export files** for a CSV or Excel of every call, or a **Webhook URL** to post the summary to Slack, Zapier, Make, n8n or your own API.

`callStatus` is `ok` for a scored call. Anything else (`no_speech`, `no_audio`, `not_supported`, `http_error`, `unreachable`, `too_large`, `review_failed`, `skipped_budget`...) is explained in `statusDetail` and never charged.

### Pricing

Pay per event, no subscription:

- **$0.04 per started minute of call**, which covers the transcript with speakers and the full review.
- $0.01 per CSV or Excel export file, $0.02 per delivered webhook, and a tiny start fee of $0.00005 per run.
- Apify subscribers on Bronze, Silver and Gold plans get 10%, 20% and 30% off.

Worked examples: an AI receptionist taking 200 calls a month at about 2 minutes each is 400 minutes, about **$16 a month** to review every single call. A 10-person sales team's 1,000 calls of 5 minutes is about **$200**, against hours of manager listening time. Each call is rounded up to the next whole minute.

Set **Max minutes per call** and Apify's **maximum charge per run** to cap any run exactly.

### How it works

Each recording is downloaded inside the run, converted to mono audio, transcribed with speaker labels by OpenAI `gpt-4o-transcribe-diarize`, then reviewed by OpenAI `gpt-5-mini` against your business description, returning a fixed set of typed fields. The review judges only what is said on the call: it returns `null` rather than guessing a name, number or booking. Audio and transcripts are not kept after the run.

### FAQ

**Is the score reliable enough to act on?** It is a strong first pass that never gets bored on call 200. Treat it like a sharp assistant: spot-check a handful of calls, tune "About the business" until the scores match your judgement, then let it run on everything.

**Can it tell the receptionist from the caller?** Yes. It works out which speaker answered for the business (`agentSpeaker`), whether that is a person or an AI.

**Where do I get recording links?** Most phone systems and voice AI platforms export a call log with a recording URL per call. Put that export (CSV or Google Sheet) straight in.

**Is call audio personal data?** Usually, yes. You stay in control: recordings are processed inside your run and deleted at the end of it, and nothing is stored by this Actor. Make sure your callers are told calls may be recorded, as you would anyway.

**Can an AI agent run it?** Yes. It charges per event, so agents can call it through the Apify MCP server or pay per run with x402.

If this showed you calls you would otherwise never have listened to, a review on the Store helps a lot. Questions or a recording format that will not read? Open an issue on the Issues tab and I will look at it the same day.

# Actor input Schema

## `datasetId` (type: `string`):

An Apify dataset holding one row per call, for example a call log exported from your phone system or CRM. Every original column is kept (call ID, date, client, agent) and the score, outcome, missed opportunities and transcript are added alongside. Use the picker rather than typing an ID.

## `fileUrl` (type: `string`):

A public link to a CSV, TSV, Excel, JSON or JSON Lines file holding one row per call with a recording link (GoHighLevel, Vapi, Retell, Twilio, CallRail, Aircall or any phone system export). A normal Google Sheets link works: share it as 'Anyone with the link can view'.

## `fileFormat` (type: `string`):

Leave on 'Detect automatically' unless the link has no file extension and the server reports the wrong content type.

## `sheetName` (type: `string`):

Which sheet to read from an Excel workbook. Defaults to the first sheet.

## `recordingUrls` (type: `array`):

A plain list of direct links to call recordings (MP3, WAV, M4A, MP4 and more; Google Drive and Dropbox share links work), for a quick run with no spreadsheet. Use the dataset, file or Google Sheet inputs above to keep your own columns alongside each score.

## `data` (type: `array`):

Rows as inline JSON, an alternative to a dataset or file. Each object needs a field holding the recording link.

## `urlField` (type: `string`):

The column holding the call recording link. Left empty it is detected automatically, preferring a column whose values end in .mp3, .wav or .m4a over one merely named 'url'.

## `businessContext` (type: `string`):

A few lines on who answers these calls and what a good call looks like: the trade or business, area covered, services, whether prices are given on the phone, and what the receptionist should do (book a visit, take name, number and postcode, flag emergencies). The review judges every call against this. Left empty, the trade is inferred from each call.

## `customQuestions` (type: `array`):

Up to 10 extra questions to answer for every call, for example 'Did the receptionist mention the free quote?' or 'Which competitor did the caller mention?'. Answers appear in 'customAnswers' and do not change the price.

## `language` (type: `string`):

Two-letter code of the spoken language (en, es, fr, de, pt, it, nl, pl, ja, zh and about 50 more). Leave empty to detect it automatically; setting it helps accuracy on short or noisy clips.

## `includeTranscript` (type: `boolean`):

Add the full call transcript, one line per speaker turn, so any score can be checked against what was actually said. Turn off for scores only.

## `includeSegments` (type: `boolean`):

Speakers mode only. Adds a 'segments' array: one entry per speaker turn with speaker, start, end and text. Useful for feeding an AI agent or building your own player.

## `maxMinutesPerFile` (type: `integer`):

Only the first this-many minutes of each file are transcribed and charged. Your cost ceiling per file: a 6-hour livestream cannot run up a bill you did not expect.

## `keep` (type: `string`):

Filtering happens after the call has been processed, so it does not make a run cheaper. 'Problems only' is the quick way to find broken recording links.

## `keepOriginalFields` (type: `boolean`):

Keep every column from the input row next to the score, so results line up with your own call log (call ID, date, client, agent). Turn off for review fields only.

## `concurrency` (type: `integer`):

How many calls to work on in parallel. Reviews run on a remote service, so this is mostly limited by download speed; 3 suits the default memory.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one file to download before giving up on it. A timed-out file is never charged.

## `maxFileMb` (type: `integer`):

Files bigger than this are skipped and not charged. Video files are large; only their audio is used.

## `maxItems` (type: `integer`):

A hard ceiling on how many rows are read from the input, as a safety net on a large dataset.

## `exportFormats` (type: `array`):

Also write the results as a real downloadable CSV or Excel file, linked from the run's output. List fields are joined with semicolons so they fit a spreadsheet.

## `outputDatasetName` (type: `string`):

Also append every kept row to a named dataset that persists across runs, building one growing call history. Not charged again.

## `webhookUrl` (type: `string`):

POST the run summary to this URL when the run finishes, for Slack, Zapier, Make, n8n or your own API. Charged only on a confirmed 2xx response.

## Actor input object example

```json
{
  "fileFormat": "auto",
  "recordingUrls": [
    "https://nerolabs-samples.nerolabs.workers.dev/sample-call-electrician.mp3",
    "https://nerolabs-samples.nerolabs.workers.dev/sample-call-plumber.mp3"
  ],
  "businessContext": "UK electricians and plumbers. The receptionist (often an AI) should take the caller's name, phone number and postcode, treat no power, leaks and burning smells as emergencies, and either book a visit or promise a callback within a set time.",
  "customQuestions": [
    "Did the receptionist take a phone number?",
    "Was a callback time promised?"
  ],
  "includeTranscript": true,
  "includeSegments": false,
  "maxMinutesPerFile": 120,
  "keep": "all",
  "keepOriginalFields": true,
  "concurrency": 3,
  "requestTimeoutSecs": 120,
  "maxFileMb": 500,
  "exportFormats": []
}
```

# Actor output Schema

## `results` (type: `string`):

Every original row with the score, outcome, missed opportunities, next step and transcript added.

## `summary` (type: `string`):

Average score, booking rate, outcome counts, minutes charged, export links and warnings.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "fileUrl": "",
    "recordingUrls": [
        "https://nerolabs-samples.nerolabs.workers.dev/sample-call-electrician.mp3",
        "https://nerolabs-samples.nerolabs.workers.dev/sample-call-plumber.mp3"
    ],
    "businessContext": "UK electricians and plumbers. The receptionist (often an AI) should take the caller's name, phone number and postcode, treat no power, leaks and burning smells as emergencies, and either book a visit or promise a callback within a set time.",
    "customQuestions": [
        "Did the receptionist take a phone number?",
        "Was a callback time promised?"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nerolabs/call-score").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "fileUrl": "",
    "recordingUrls": [
        "https://nerolabs-samples.nerolabs.workers.dev/sample-call-electrician.mp3",
        "https://nerolabs-samples.nerolabs.workers.dev/sample-call-plumber.mp3",
    ],
    "businessContext": "UK electricians and plumbers. The receptionist (often an AI) should take the caller's name, phone number and postcode, treat no power, leaks and burning smells as emergencies, and either book a visit or promise a callback within a set time.",
    "customQuestions": [
        "Did the receptionist take a phone number?",
        "Was a callback time promised?",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("nerolabs/call-score").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "fileUrl": "",
  "recordingUrls": [
    "https://nerolabs-samples.nerolabs.workers.dev/sample-call-electrician.mp3",
    "https://nerolabs-samples.nerolabs.workers.dev/sample-call-plumber.mp3"
  ],
  "businessContext": "UK electricians and plumbers. The receptionist (often an AI) should take the caller'\''s name, phone number and postcode, treat no power, leaks and burning smells as emergencies, and either book a visit or promise a callback within a set time.",
  "customQuestions": [
    "Did the receptionist take a phone number?",
    "Was a callback time promised?"
  ]
}' |
apify call nerolabs/call-score --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nerolabs/call-score"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/OhrgmYGGNpigmYGQM/builds/vAubBmWdBvNMZAADD/openapi.json
