---
title: "Get a run's decision summary"
method: GET
path: "/projects/{projectId}/eval-runs/{runId}/decision-summary"
tags: ["Eval runs"]
---

# Get a run's decision summary

`GET /projects/{projectId}/eval-runs/{runId}/decision-summary`

The canonical, versioned run decision contract: the verdict and where it came from, the counts with the population they count, the run's own verdict decision when it has one, and one page of per-trial diagnostics carrying the user-value chain, the first failed stage, the failure category, evidence scoped to that stage, and one next action.

ADDITIVE and composed: it is the same two reads a caller could make by hand (`GET …/eval-runs/{runId}` and `GET …/iterations`), assembled once here so every client shares one reading of a run instead of inventing its own. `cursor` and `limit` page the DIAGNOSTICS using the same cursors the iterations endpoint issues, and `diagnostics.complete` says honestly whether the page you got is the run's whole non-passing set.

## Path parameters

- `projectId` string, required
- `runId` string, required

## Query parameters

- `limit` integer
- `cursor` string

## Response `200`

The run's decision summary.

- EvalRunDecisionSummary — The canonical run decision contract (schema version 1): what the run decided, in what unit it counted, and what evidence sits under the non-passing trials. START HERE when a run did not pass. It EXPLAINS the verdict and never DECIDES it. Under verdict policy v2 the authority is the run's own decision — its verdict, rates, validity phase, reasons, per-case stability and mixed-verdict flags are copied after validation and never recomputed from the trials; `counts` is a tally of `decision.cases[].verdict` and nothing more. `verdict` has a fourth value the run verdict vocabulary does not: `notEstablished` means no verdict exists at all (the run is unfinished, stopped early, or its decision could not be read). It is not a failure and not `inconclusive` — `inconclusive` is a decision the validity phase reached, and this is the absence of any decision.
  - `schemaVersion` 1, required
  - `runId` string, required
  - `runStatus` string, required — The run's lifecycle status, verbatim. Not a verdict.
  - `verdict` 'passed' | 'failed' | 'inconclusive' | 'notEstablished', required
  - `verdictSource` 'policyV2' | 'legacy' | 'none', required — `policyV2` — the run's own decision, carried on `decision`; `verdict` is its verdict. `legacy` — a percent-threshold run predating policy v2; there is no decision object and any counts are trials. `none` — no verdict; `verdict` is `notEstablished` and `undecided` says why.
  - `counts` union — A tally WITH the population it counted. `measurementUnit` is never omitted and never inferred: under verdict policy v2 the counts are case-execution VARIANTS (repetitions are trials inside one, not members of it), and on a legacy run they are trials. A 3-case suite with 5 repetitions is legitimately 3 under one unit and 15 under the other, so a count reported without its unit is not a fact. On a `caseVariant` count every field is present and the three buckets sum to `total`; on a `trial` count each field is present only if the legacy run recorded it — absence stays absence rather than defaulting to zero.
    - object
      - `measurementUnit` 'caseVariant', required
      - `total` integer, required
      - `passed` integer, required
      - `failed` integer, required
      - `inconclusive` integer, required — `caseVariant` only. A legacy run has no such bucket.
    - object
      - `measurementUnit` 'trial', required
      - `total` integer
      - `passed` integer
      - `failed` integer
  - `decision` object — The run's authoritative v2 verdict decision, copied verbatim after validation — the same shape as `EvalRun.verdictSummary`, published at https://mcpjam.com/schemas/eval-verdict-policy/v2.json. Present exactly when `verdictSource` is `policyV2`.
  - `undecided` EvalRunDecisionUndecided — Why no verdict was established. Present exactly when `verdictSource` is `none`.
    - `reason` 'runNotTerminal' | 'runStatusNotAVerdict' | 'runResultNotAVerdict' | 'verdictSummaryUnavailable', required — `runNotTerminal` — still pending or running; poll it. `runStatusNotAVerdict` — a LEGACY run that stopped at cancelled/timed_out/failed, so its stored counts describe the iterations it happened to record and gating on them is fail-open (a policy-v2 run is deliberately not resolved this way: its validity phase is where lifecycle enters the verdict). `runResultNotAVerdict` — completed with no recognizable result. `verdictSummaryUnavailable` — decided under policy v2 with no readable decision; there is no legacy fallback, because re-deciding a v2 run under percent-threshold semantics would answer a question nobody asked.
    - `detail` string — The platform's own message, when it supplied one. Never synthesized.
  - `diagnostics` EvalRunDecisionDiagnostics, required — One page of diagnostics, with its completeness stated rather than implied. `complete` is true ONLY when `items` is the whole non-passing set for the run: a page reached through a cursor is never complete however few rows follow it. `scannedIterations` is how many trials this page examined, which is what separates "we looked at 50 and none failed" from "we did not look" — both otherwise render as an empty `items`.
    - `items` EvalRunDecisionDiagnostic[], required
      - `iterationId` string, required
      - `iterationNumber` integer, required
      - `caseId` string — The case's SDK-declared id, when the run recorded one. NOT a join key into `decision.cases[].caseId`.
      - `testCaseId` string — The stored case row id.
      - `title` string
      - `status` string, required — LIFECYCLE, not verdict.
      - `result` 'passed' | 'failed' — Task verdict once terminal. Absent when the trial never produced one.
      - `chain` union, required — Whether this trial's user-value chain can be believed. `verified` — the stored derivation validated, and ONLY this state carries `stages`, `firstFailedStage` or `failureCategory`. `unverified` — a derivation was stored and did not validate; the chain and both claims derived from it are withheld, because they are assertions ABOUT rows that did not validate. `absent` — no derivation was stored at all, which is a different fact from one that was rejected.
        - object
          - `status` 'verified', required
          - `stages` StageResultRow[], required — Always six rows, in chain order. `verified` only.
            - `stage` 'connection' | 'discovery' | 'selection' | 'call' | 'response' | 'userValue', required — Which link of the user-value chain this row is a verdict on. The order is NORMATIVE: a run walks the six in sequence, and `notReached` is derived from position — every stage after the first failed one never ran. `connection` — the server was reachable and the session initialized. `discovery` — its tools and resources were listed and readable. `selection` — the model chose the right tool for the request. `call` — the call was made with usable arguments. `response` — the server returned data the model could use. `userValue` — the user's actual request was satisfied.
            - `state` 'passed' | 'failed' | 'notReached' | 'notMeasured' | 'notApplicable', required — What this stage did. The three non-verdicts stay three different facts on purpose: collapsing them is how "we never checked" gets read as "it passed". `passed` — measured, and it passed. `failed` — measured, and it failed. `notReached` — it never ran (an earlier stage failed), so there was nothing to decide. `notMeasured` — this run captured nothing that could decide it, so the stage is not measured. `notApplicable` — the stage is not applicable to this case at all.
            - `reason` 'noSpanChannel' | 'noEvidenceCaptured' | 'matchVerdictUnavailable' | 'traceAbsent' | 'executorEmitsNoSpans' | 'blockedByPolicy' | 'evaluatorError' | 'providerError' | 'setupAborted' | 'connectFailed' | 'toolsListFailed' | 'egressUnverified' | 'lifecycleStopped' | 'notAuthored' | 'earlierStageFailed' | 'missingToolCall' | 'unexpectedToolCall' | 'argumentMismatch' | 'toolError' | 'protocolError' | 'renderFailed' | 'predicateFailed' | 'observed' | 'impliedByLaterEvidence' | 'judgeObserved' | 'judgePartial' | 'judgeFailed' | 'judgePending' | 'judgeNotRequested' — Why this stage landed where it did. A CLOSED vocabulary — render what arrives, never widen it. Each entry completes "…because <reason>". `noSpanChannel` — this run captures no evidence channel for that stage. `noEvidenceCaptured` — nothing eligible for that stage was captured. `matchVerdictUnavailable` — extra tool calls were captured but the run did not report whether its match options tolerate them. `traceAbsent` — the iteration recorded no trace. `executorEmitsNoSpans` — the executor emitted no spans. `blockedByPolicy` — a policy blocked the run before it could be measured. `evaluatorError` — the evaluator itself failed, so the run says nothing about the server. `providerError` — the model provider failed the call, so this stage was never measured. `setupAborted` — the environment was never prepared, so the test never began. `connectFailed` — the configured server was reached and initialize failed there. `toolsListFailed` — initialize succeeded and listing tools failed. `egressUnverified` — the connection failed with no evidence that our own network egress works. `lifecycleStopped` — the run was stopped mid-flight. `notAuthored` — the case asserts nothing this stage could decide. `earlierStageFailed` — an earlier stage failed. `missingToolCall` — an expected tool call was never made. `unexpectedToolCall` — a tool call was made that the case did not expect. `argumentMismatch` — the call arguments did not match what the case expects. `toolError` — the server reported a tool error. `protocolError` — the call never produced a result. `renderFailed` — the widget did not render. `predicateFailed` — a check on the result did not hold. `observed` — the evidence was inspected and the stage held. `impliedByLaterEvidence` — a later stage's success implies it. `judgeObserved` — the LLM judge scored at or above the threshold. `judgePartial` — the LLM judge scored inside the partial band — at or above the floor, below the threshold. `judgeFailed` — the LLM judge scored below the partial floor. `judgePending` — an LLM judge verdict is owed and has not arrived. `judgeNotRequested` — no LLM judge verdict was ever owed.
            - `evidence` object
              - …
          - `firstFailedStage` 'connection' | 'discovery' | 'selection' | 'call' | 'response' | 'userValue' — Where the chain stopped — A LOCATION, NOT A CAUSE. It names the first link that failed, never why it failed: the why is `reason` on that stage's row (see `StageResultRow.reason`), and neither this nor the `failureCategory` bucket on its own authorizes proposing a change to the server. Absent when nothing failed — and absent on a trial that never reached a stage at all, such as a setup abort or an evaluator error, which still carries a `failureCategory`. `connection` — the server was reachable and the session initialized. `discovery` — its tools and resources were listed and readable. `selection` — the model chose the right tool for the request. `call` — the call was made with usable arguments. `response` — the server returned data the model could use. `userValue` — the user's actual request was satisfied.
          - `failureCategory` 'setup' | 'metadata' | 'selection' | 'arguments' | 'serverData' | 'userValue' | 'evaluator' — The bucket this trial is grouped under — A BUCKET, NOT A DIAGNOSIS. Present WITHOUT `firstFailedStage` for a setup abort or an evaluator error — both are real answers about a run that never reached a stage. `setup` — setup: the harness or environment never got to the test. `metadata` — tool metadata: tool names, descriptions or schemas misled the model. `selection` — tool selection: the model picked the wrong tool, or none. `arguments` — call arguments: the right tool, called wrongly. `serverData` — server data: the server answered, with data the model could not use. `userValue` — user value: everything mechanical worked and the user still was not served. `evaluator` — evaluator: the grader itself failed, so the run says nothing about the server — never folded into the others, because counting a broken judge as a server defect poisons every rate derived from it.
          - `analyzerVersion` integer, required
          - `analyzerVersionAhead` object — Present when the rows came from an analyzer NEWER than the reader knows. Flagged, never rejected: blanking the chain every time the platform ships ahead of a pinned client would be worse than showing it with a caveat.
            - `reported` integer, required
            - `known` integer, required
        - object
          - `status` 'unverified', required
          - `analyzerVersion` integer
          - `analyzerVersionAhead` object
            - `reported` integer, required
            - `known` integer, required
        - object
          - `status` 'absent', required
      - `expected` object
        - `toolNames` string[], required
      - `observed` object
        - `toolNames` string[]
        - `failure` string
      - `evidence` EvalRunDecisionEvidence, required — Where to look. For a measured failure the span ids, prompt indexes and reasons are read from the FIRST FAILED STAGE'S ROW and from nowhere else — unioning the passing stages in would hand back the evidence of everything that worked, labelled as the explanation of the thing that did not. A stage-less outcome (setup abort, evaluator error) keeps a stage-less locator rather than inventing a stage to hang the link on.
        - `runId` string, required
        - `iterationId` string, required
        - `stage` 'connection' | 'discovery' | 'selection' | 'call' | 'response' | 'userValue' — The stage this evidence was read from, and the only stage it is evidence ABOUT. Absent when none was established. `connection` — the server was reachable and the session initialized. `discovery` — its tools and resources were listed and readable. `selection` — the model chose the right tool for the request. `call` — the call was made with usable arguments. `response` — the server returned data the model could use. `userValue` — the user's actual request was satisfied.
        - `spanIds` string[]
        - `promptIndexes` integer[]
        - `reasons` string[]
        - `tracePath` string, required — The iteration-trace endpoint's path RELATIVE TO THE API ROOT (e.g. `/projects/{projectId}/eval-runs/{runId}/iterations/{iterationId}/trace`), so it resolves against any deployment's base URL rather than baking one host into a stored artifact.
      - `nextAction` string, required — One operator action, keyed on the failure category alone. With no category it says to go and look rather than naming a system, because nothing established which one is involved.
    - `complete` boolean, required
    - `nextCursor` string — Pass back as `cursor`. Absent on the last page.
    - `scannedIterations` integer, required

## Other responses

- `401` — Missing, invalid, revoked, or orphaned key (`UNAUTHORIZED`) — or the **target MCP server** needs an OAuth grant (`OAUTH_REQUIRED`), which is a property of the server, not your key.
- `403` — Key is valid but not allowed to do this.
- `404` — Unknown project, server, or resource.
- `429` — Per-key rate limit exceeded (60 requests/minute sustained, bursts up to 10). Honor `Retry-After` and back off with jitter.
- `500` — Something failed on MCPJam's side.

## Changes

- **2026-09-01** `b7c1342bf59e` — 6 warning
  - added the new `judgeFailed` enum value to the `diagnostics/items/items/chain/oneOf[subschema #1]/stages/items/reason` response property for the response status `200`
  - added the new `judgeNotRequested` enum value to the `diagnostics/items/items/chain/oneOf[subschema #1]/stages/items/reason` response property for the response status `200`
  - added the new `judgeObserved` enum value to the `diagnostics/items/items/chain/oneOf[subschema #1]/stages/items/reason` response property for the response status `200`
  - added the new `judgePartial` enum value to the `diagnostics/items/items/chain/oneOf[subschema #1]/stages/items/reason` response property for the response status `200`
  - …2 more
- **2026-08-26** `eaee3e3581d2` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/mcpjam/apis/mcpjam-api/changes/projects/:projectId/eval-runs/:runId/decision-summary/get.md)

---

[API](https://skmtc.dev/mcpjam/apis/mcpjam-api.md) · [All operations](https://skmtc.dev/mcpjam/apis/mcpjam-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/mcpjam/mcpjam-api/revisions/d1e33646f9d8/schema)
