> ## Documentation Index
> Fetch the complete documentation index at: https://docs.upliftai.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Scorecards

A scorecard turns every call into a graded record: yes/no criteria with points, summing to 100. After each call, an LLM reads the transcript and answers each criterion with pass, fail, or N/A. No partial credit.

**The scorecard gates the whole analysis, not just the score.** Conversions, callbacks, and sentiment all ride the same grading pass. No scorecard means no score, no conversion rows, no sentiment — the call still records and transcribes, but nothing gets extracted.

## Setup

Two places to put one, same shape:

* **Assistant level** — `config.grading.scorecard` on [create](/api-reference/assistants/create-an-assistant) or [update assistant](/api-reference/assistants/update-an-assistant). Grades every non-campaign call: web, inbound, direct API dials. Editable any time.
* **Campaign level** — `config.scorecard` on [create campaign](/api-reference/campaigns/create-a-campaign). Grades that campaign's calls. Strictly either/or: a campaign call uses the campaign's scorecard only. A campaign without one is not graded at all, even when its assistant has one. Locks at launch, so get it right in `draft`.

```json theme={null}
{
  "grading": {
    "scorecard": {
      "rubricVersionId": "v1",
      "rules": [
        { "ruleId": "right-person", "title": "Confirmed the right person", "points": 15, "instruction": "The agent confirmed they were speaking to the named patient before discussing the appointment.", "scoringType": "binary", "evidenceRequired": true },
        { "ruleId": "purpose", "title": "Stated purpose clearly", "points": 15, "instruction": "The agent said why they were calling within the first two turns.", "scoringType": "binary", "evidenceRequired": true },
        { "ruleId": "booked", "title": "Booked an appointment", "points": 35, "instruction": "The caller agreed to a specific appointment time on this call. Mark N/A if the wrong person answered.", "scoringType": "binary", "evidenceRequired": true },
        { "ruleId": "reference", "title": "Gave a reference number", "points": 10, "instruction": "The agent read a booking reference back to the caller.", "scoringType": "binary", "evidenceRequired": true },
        { "ruleId": "tone", "title": "Warm, professional tone", "points": 15, "instruction": "The agent stayed polite and calm, including when the caller pushed back.", "scoringType": "binary", "evidenceRequired": false },
        { "ruleId": "scope", "title": "Stayed in scope", "points": 10, "instruction": "The agent did not discuss topics outside the appointment.", "scoringType": "binary", "evidenceRequired": true }
      ]
    }
  }
}
```

The API enforces the shape at write time. Points are positive integers that **sum to exactly 100**. Every rule needs a unique `ruleId`, a `title`, and an `instruction`. `scoringType` is `binary` — anything else is a 400.

## How grading runs

* After the call ends, one LLM pass reads the transcript and answers each rule.
* The transcript includes every tool call and its result — "the agent actually booked" is checkable.
* The score is recomputed server-side from the per-rule answers. The model doesn't get to invent a total.
* N/A rules **drop out of the score**. The score is `round(100 × earned ÷ applicable points)`, so 40 earned of 60 applicable is **67**, not 40.
* If no rule applied, the score is **`null`**. Check for `null` before you compare. In JavaScript, `null < 60` is `true`.
* Connected calls only. If the caller never spoke, it's a silent pickup and skips grading.
* If the grader's output is malformed twice in a row, the call is marked `analysis_failed` and gets no score rather than a wrong one.

## Writing criteria

One rule: **a criterion must be answerable from the transcript alone.** The grader cannot see your CRM, the caller's intent, or whether the price quoted was correct. And the grader is instructed that unclear, ambiguous, or merely implied evidence scores **zero**. Vague criteria don't score fuzzily. They score 0.

**Put the N/A condition in the instruction.** The grader decides whether a rule applies from its instruction, like `booked` above: "Mark N/A if the wrong person answered." Missing evidence or a skipped step is never N/A. It's a fail.

Observable beats vibes. Write criteria as acts: "read the reference number back," "confirmed the right person," "stayed in scope." "Was empathetic" mostly has no citable moment, so it scores 0.

One boundary: instructions are data, not commands. You can't use an `instruction` to ask for partial credit, and nothing said *on the call* can override the grading protocol.

## Points

Points are the only importance signal — put the weight on the outcome. In the example above, `booked` is 35 of 100. Five to eight rules is plenty: grading is all-or-nothing per call, and integer points summing to 100 get harder to split meaningfully with every rule you add.

## Evidence

Set `evidenceRequired: true` and every answer (failures included) must carry a timestamp and a quote from the transcript. Every point becomes auditable. Cheap insurance. An N/A always carries a reason, and usually a quote, whatever the flag says.

## Where scores land

* **[Session detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes)** has everything: the score and the full per-criterion breakdown, next to the transcript it came from. Each criterion carries its `status` (`pass`, `fail`, or `not_applicable`), plus a reason and evidence when the grader gave them. An N/A shows as N/A, never as a fail: `passed` is `null`. The grade is self-contained — rule titles and points are written into it, so you never need the rubric to read a result.
* **Campaign surfaces** roll them up. [Stats](/api-reference/campaign-outcomes/aggregate-stats-for-a-campaign) carries `avgScore` and the bands: `great` ≥ 80, `ok` 60–79, `low` \< 60, plus `analysisFailed`. A call with a `null` score sits outside `avgScore` and the bands. The [calls list](/api-reference/campaign-outcomes/list-a-campaigns-calls) shows each call's `score` and `sentiment`.
* **The `call.graded` webhook** fires with `{ callId, score }` as each grade lands, and `score` can be `null`. Use the webhook to alert yourself on low scores — and call back the customers whose calls didn't go well.
