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

# Analyze Run

> Run AI analysis over a run's results and store the structured report

Runs an LLM analysis over the run's scored results and stores a structured narrative report:
overall summary, consistency, instruction adherence, response patterns, reasoning quality, tool
usage, and prioritized recommendations. Fetch the stored report with
[Get Report](/api-reference/custom-eval/get-report).

The analysis prompt samples the **worst 12 and best 5** rated results (all of them when the run
has 17 or fewer), and each sampled result is independently re-scored by up to 3 judge models to
produce per-item agreement evidence.

<Note>
  On the self-host engine this endpoint is **synchronous**: it returns only once the judges are
  done, so the response status is already terminal (`"completed"` or `"failed"`), and the first
  `GET /runs/:runId/analyze-status` poll reads that same terminal status. The `mode: "sync"`
  field says so on the wire. (Hosted AgentX queues a durable job and returns `"pending"`
  instead; the SDK's poll loop handles both.) Re-running analysis overwrites the stored row.
</Note>

## Authentication

<ParamField header="x-api-key" type="string" required>
  Project API key.
</ParamField>

## Path Parameters

<ParamField path="runId" type="string" required>
  Run ID. Analyze after [finalizing](/api-reference/custom-eval/finalize-run), so the analysis
  covers every result.
</ParamField>

## Body

<ParamField body="judges" type="array">
  Judge models to score the evidence sample with, up to 3: `[{ "model": string }]`. Model ids
  come from the [model catalog](/api-reference/custom-eval/list-models). Duplicate models are
  deduplicated before the 3-model cap is applied. The first entry also writes the narrative.
  Omit to use the default judge model alone.
</ParamField>

<ParamField body="qualityMode" type="string" default="&#x22;balanced&#x22;">
  `"balanced"` or `"quality_first"`. Echoed back in the response; not persisted with the stored
  report.
</ParamField>

<Note>
  A second analyze request for the same run while one is already in flight joins the running
  analysis and returns its result instead of starting a duplicate - so an immediate retry does
  not double your judge spend or re-run the analysis.
</Note>

## Response

<ResponseField name="evaluationId" type="string">
  The run ID (analysis is keyed by run).
</ResponseField>

<ResponseField name="jobId" type="string">
  Same value as `evaluationId`, kept for SDK compatibility with the hosted job queue.
</ResponseField>

<ResponseField name="status" type="string">
  `"completed"`, or `"failed"` (for example, when the run has no scored results yet, or the
  judge model returned no analysis - the failure reason is readable from
  `GET /runs/:runId/analyze-status`).
</ResponseField>

<ResponseField name="mode" type="string">
  Always `"sync"` on self-host.
</ResponseField>

<ResponseField name="qualityMode" type="string">
  Echoed back.
</ResponseField>

## Errors

| Status | Body                           | Meaning                            |
| ------ | ------------------------------ | ---------------------------------- |
| `404`  | `{ "error": "Run not found" }` | No run with this id in the project |

## Checking status

`GET /api/v1/custom-agent-evaluations/runs/{runId}/analyze-status` returns
`{ evaluationId, jobId, status, progress, failureReason, warnings, cost, etaUpdatedAt, overflowStats }`.
`status` is `"not_started"` before any analysis, `"running"` while one is in flight for the
run (a concurrent or retried analyze), otherwise the stored row's terminal `"completed"` or
`"failed"`; a failure carries `failureReason: { code: "ANALYSIS_FAILED", message, retryable:
true }`. Poll loops must treat any non-terminal value as "keep waiting", not just
`"not_started"`. All three shapes carry the same keys; `running` and terminal rows carry
`jobId` set to the run ID, and only `not_started` carries `jobId: null`. On terminal rows the
progress/cost/overflow fields exist for wire compatibility with the hosted job queue and are
static on self-host. An unknown run returns `404 { "error": "Run not found" }`.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST http://localhost:4700/api/v1/custom-agent-evaluations/runs/rK7dP2qWx9TzB4mV6nJcE/analyze \
    -H "x-api-key: agtx_local_0f3c9a17d2b84e6a5c01b9f4e7d8a2c6431b5f97a0e2d4c8" \
    -H "Content-Type: application/json" \
    -d '{ "judges": [{ "model": "gpt-5.6-luna" }, { "model": "claude-opus-5" }] }'
  ```

  ```python Python SDK theme={null}
  client.evaluations.analyze_run("rK7dP2qWx9TzB4mV6nJcE")
  report = client.evaluations.get_report("rK7dP2qWx9TzB4mV6nJcE")
  print(report.summary)
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "evaluationId": "rK7dP2qWx9TzB4mV6nJcE",
    "jobId": "rK7dP2qWx9TzB4mV6nJcE",
    "status": "completed",
    "mode": "sync",
    "qualityMode": "balanced"
  }
  ```

  ```json 200 OK (nothing to analyze) theme={null}
  {
    "evaluationId": "rK7dP2qWx9TzB4mV6nJcE",
    "jobId": "rK7dP2qWx9TzB4mV6nJcE",
    "status": "failed",
    "mode": "sync",
    "qualityMode": "balanced"
  }
  ```

  ```json 404 Not found theme={null}
  {
    "error": "Run not found"
  }
  ```
</ResponseExample>
