# Get Verdicts

- **Method:** `GET`
- **Path:** `/api/v1/runs/{run_id}/verdicts`
- **Operation ID:** `get_verdicts`
- **Tags:** artifact

Each persona's verdict at the end of its session - its answer to the run's verdict question, quoted
from its own diary - and the cohort's tally. Persona feedback: subjective, to be attributed to the
persona, and never a ticket on its own. An empty list with the run's `status` until the run is done.

## Effective servers

- `https://api.saram.sh`

  Saram

## Authentication

- **HTTPBearer**: HTTP bearer

  a Saram API key (`sk_saram_<env>_...`) or a person's AuthKit access token, as `Authorization: Bearer <credential>`. Both are told apart by shape and produce the same principal.

## Path parameters

- **`run_id` (required)**: `string`

## Responses

### 200 Successful Response

**Content type:** `application/json`

schema: `VerdictList`, no additional properties

A run's persona verdicts: each session's answer to the run's verdict question, grounded in the persona's
own diary quotes, and the cohort's tally. Persona feedback, never a ticket on its own.

- **`advisory` (required)**: `string`

  How to read this whole answer: persona feedback, not defects. Never a ticket on its own.
- **`run_id` (required)**: `string`
- **`status` (required)**: `string`

  The run's status. An empty `verdicts` is only a result once this is terminal.
- **`subjective_fields` (required)**: `array of string`

  Fields on each row that are the persona's opinion - for a verdict, the verdict itself.
- **`verdicts` (required)**: `array`

  **Items:**

  schema: `VerdictRow`, no additional properties

  SUBJECTIVE: one session's verdict as the artifact's `outcomes[]` carries it. Persona feedback, never a
  ticket on its own.
  - **`mission_id` (required)**: `string`
  - **`persona_id` (required)**: `string`
  - **`result` (required)**: `string`
  - **`session_id` (required)**: `string`
  - **`observer_error`**: `string | null`

    Why no judge read this session, when none did, in Saram's words (`artifact.outcomes[].observer_error`): its
    judge pass failed after its retries, so the absent `verdict` is a question nobody asked a model, not one the
    persona left unanswered. The model provider's status and own message when the provider refused, the `providers`
    entry's refusal when it did not resolve, Saram's sentence otherwise. Absent on every session a judge read, and
    on every row of an artifact written before contracts 0.6.1; one written before 0.6.3 carries the failure as it
    was recorded.
  - **`verdict`**: `object | null`, schema: `Verdict`, no additional properties

    SUBJECTIVE. The persona's answer at session end to the run's verdict prompt - the run spec's `mission.verdict_prompt` when it has one, `persona.verdict_prompt` otherwise - grounded in diary quotes. A persona pack's question is about the product the pack was written for, so a run that points a pack at another target sets the mission's and the pack's is not applicable for that run. Which of the two this answers, and its text, are on the row: `outcomes[].verdict_prompt_source` and `outcomes[].verdict_prompt`, which sit there rather than here so that a session with no verdict still names the question that went unanswered. Consumers must tag it as subjective. `decision_text`, `reason` and `quotes` are quotation, verbatim from the diary at `step_refs`; `reason_paraphrase` is the only field written in the judge's voice, and a consumer that shows it must label it as the judge's, never as the persona's words.
    - **`decision` (required)**: `string`, possible values: `"positive", "negative", "undecided"`

      Schema: `Decision`.

      Would keep paying / would recommend / would text 'it works' = positive
    - **`reason` (required)**: `string`
    - **`step_refs` (required)**: `array of integer`, minItems: `1`
    - **`decision_text`**: `string | null`
    - **`quotes`**: `(array of string) | null`
    - **`reason_paraphrase`**: `string | null`
  - **`verdict_discarded`**: `object | null`, schema: `VerdictDiscarded`, no additional properties

    NOT A VERDICT. The answer the judge gave that the quote gate threw away because none of it could be grounded in
    the persona's diary at the steps it cited, and why (`artifact.outcomes[].verdict_discarded`). Set only on a row
    with no `verdict`; absent there means nothing was discarded - the judge returned no answer, or no judge read the
    session (`observer_error`). Never show `judge_decision` as the persona's verdict and never count it: the cohort's
    `summary` counts this session `undecided`. Absent on every row of an artifact written before contracts 0.6.3.
    - **`gate_reason` (required)**: `string`
    - **`judge_decision` (required)**: `string`, possible values: `"positive", "negative", "undecided"`

      Schema: `JudgeDecision`.

      The decision the judge returned, before the gate discarded it. The judge's and not the persona's: no diary line at the steps it cites supports it. Named apart from `verdict.decision` so that nothing reading a row by field name takes it for the persona's.
  - **`verdict_prompt`**: `string | null`

    The verdict question this session was asked, verbatim: the one `verdict` answers. Absent when no question
    was in play for the session, and on every row of an artifact written before the outcome carried it (0.4.1).
  - **`verdict_prompt_source`**: `string | null`

    Schema: `VerdictPromptSource`. Known values, open (more may be added): `mission`, `persona`.

    Whose question `verdict` answers: the run spec's `mission.verdict_prompt`, or the persona pack's when the
    mission carried none. Present exactly when `verdict_prompt` is; `mission` beside an absent
    `verdict` is a persona who was asked the run's question and did not answer it - unless `verdict_discarded`
    says the judge answered for them and could not ground the answer.
- **`summary`**: `object | null`, schema: `Verdicts`, no additional properties

  The artifact's cohort read (`summary.verdicts`).
  - **`negative`**: `integer | null`
  - **`positive`**: `integer | null`
  - **`top_reasons`**: `(array of string) | null`
  - **`undecided`**: `integer | null`

**Example:**

```json
{
  "run_id": "run_2026-09-26_4f1c2a",
  "status": "completed",
  "summary": {
    "positive": 1,
    "negative": 1,
    "undecided": 0,
    "top_reasons": [
      "The sign-up form failed without saying why.",
      "Signing up was fast."
    ]
  },
  "verdicts": [
    {
      "session_id": "run_2026-09-26_4f1c2a.sess_01_dana_first_timer",
      "persona_id": "dana_first_timer@1",
      "mission_id": "sign_up",
      "result": "abandoned",
      "verdict_prompt": "Would you use this product again after today, and what decided it?",
      "verdict_prompt_source": "mission",
      "verdict": {
        "decision": "negative",
        "decision_text": "No. If I can't even make an account, I'm not trusting it with my work.",
        "reason": "I pressed the button four times and nothing happened",
        "reason_paraphrase": "The sign-up form failed without saying why.",
        "quotes": [
          "GAVE UP: I pressed the button four times and nothing happened, so I assumed the site was broken. No. If I can't even make an account, I'm not trusting it with my work."
        ],
        "step_refs": [
          10,
          18
        ]
      }
    },
    {
      "session_id": "run_2026-09-26_4f1c2a.sess_02_marcus_fast_skimmer",
      "persona_id": "marcus_fast_skimmer@1",
      "mission_id": "sign_up",
      "result": "completed",
      "verdict_prompt": "Would you use this product again after today, and what decided it?",
      "verdict_prompt_source": "mission",
      "verdict": {
        "decision": "positive",
        "decision_text": "Yes - I was in and looking at a dashboard in under a minute.",
        "reason": "I was in and looking at a dashboard in under a minute",
        "reason_paraphrase": "Signing up was fast.",
        "quotes": [
          "FINISHED: Yes - I was in and looking at a dashboard in under a minute."
        ],
        "step_refs": [
          9
        ]
      }
    }
  ],
  "advisory": "ADVISORY, SUBJECTIVE. A verdict is the persona's own answer at the end of its session, grounded in verbatim diary quotes. It is feedback, not a defect: it never files a ticket on its own and must be presented as the persona's opinion, never as an observed fact.",
  "subjective_fields": [
    "verdict"
  ]
}
```

### 403 the API key lacks the required scope

### 422 Validation Error

**Content type:** `application/json`

schema: `HTTPValidationError`

- **`detail`**: `array`

  **Items:**

  schema: `ValidationError`
  - **`loc` (required)**: `array of (string | integer)`
  - **`msg` (required)**: `string`
  - **`type` (required)**: `string`
  - **`ctx`**: `object`
  - **`input`**: `any`

**Example:**

```json
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": null,
      "ctx": {}
    }
  ]
}
```

## Schemas

### Decision

**Type:** `string`, possible values: `"positive", "negative", "undecided"`

Would keep paying / would recommend / would text 'it works' = positive

- `HTTPValidationError` — shown above.

### JudgeDecision

**Type:** `string`, possible values: `"positive", "negative", "undecided"`

The decision the judge returned, before the gate discarded it. The judge's and not the persona's: no diary line at the steps it cites supports it. Named apart from `verdict.decision` so that nothing reading a row by field name takes it for the persona's.

- `ValidationError` — shown above.
- `Verdict` — shown above.
- `VerdictDiscarded` — shown above.
- `VerdictList` — shown above.

### VerdictPromptSource

**Type:** `string`

Known values, open (more may be added): `mission`, `persona`.

Whose question `verdict` answers: the run spec's `mission.verdict_prompt`, or the persona pack's `persona.verdict_prompt` when the mission carried none. `mission` is also the record that the pack's own question was not applicable for this run, which is what makes an absent `verdict` beside it readable: the diary did not answer a question about the product actually under test, which is a run-spec problem, not a judge that could not ground itself (that one names its discarded answer in `verdict_discarded`). Absent means no verdict question was in play: neither the mission nor the pack carried one, so `verdict` is absent by construction and `summary.verdicts` counts the session `undecided` without that being anything the judge failed at. An artifact written before this release carries none either, and `contracts_version` is what tells the two apart. Absent beside `observer_error` is neither: no judge ran, so nobody resolved which question was in play.

**Example:**

```json
"mission"
```

**Example:**

```json
"persona"
```

- `VerdictRow` — shown above.
- `Verdicts` — shown above.
