# Get Opportunities

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

What the personas' behaviour says the product could do better, or already does well - unmet
expectations, missing capabilities, value gaps, delight - scored by how many sessions share it. Persona
feedback, never a ticket on its own. `display` says which clear the cohort floors and which are
single-persona notes. 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: `OpportunityList`, no additional properties

A run's opportunities: behaviour-grounded persona feedback that is not a defect - unmet expectations,
missing capabilities, value gaps, delight - scored by how many personas share it. Never a ticket on its own.

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

  How to read this whole answer: behaviour-grounded persona feedback, consensus-scored, never a ticket on its
  own.
- **`display` (required)**: `object`, schema: `DisplaySplit`, no additional properties

  The run's shown/folded split, present even when the run raised nothing: the floors are the answer to
  "why is this list short", and they are the same whether or not anything cleared them.
  - **`folded` (required)**: `array of string`
  - **`min_affected_sessions` (required)**: `integer`
  - **`min_consensus` (required)**: `number`
  - **`note` (required)**: `string`
  - **`shown` (required)**: `array of string`
  - **`strong_consensus` (required)**: `number`
- **`opportunities` (required)**: `array`

  **Items:**

  schema: `Opportunity`, no additional properties

  Behavior-grounded persona feedback that is not a defect: an unmet expectation, a missing capability, a value or trust gap, or a moment of delight. Scored by consensus across the sessions that reached the area, never by reproduction, and never a ticket on its own. `description` is the objective account; `persona_rationale` and `suggested_change` are opinion.
  - **`affected_sessions` (required)**: `array of string`
  - **`consensus` (required)**: `number`, minimum: `0`, maximum: `1`
  - **`description` (required)**: `string`
  - **`evidence` (required)**: `object`, schema: `Evidence`, no additional properties

    What the feedback rests on: the steps where personas raised it (`step_refs`), lines from their diaries at those steps, each checked to appear there verbatim (`quotes`), and screenshots of what they were looking at.
    - **`step_refs` (required)**: `array`, minItems: `1`

      **Items:**

      schema: `StepRef`

      One step of one session: the session's id and the step's number within it. A finding points at the steps it rests on, and that session's step events say what happened at each.
      - **`session_id` (required)**: `string`
      - **`step_id` (required)**: `integer`
    - **`quotes`**: `(array of string) | null`
    - **`screenshots`**: `array | null`

      **Items:**

      `string`, format: `uri`, minLength: `1`
  - **`id` (required)**: `string`
  - **`kind` (required)**: `string`

    Schema: `Kind`. Known values, open (more may be added): `unmet_expectation`, `missing_capability`, `value_gap`, `trust_gap`, `delight`.

    What sort of feedback this is. `unmet_expectation`: the product did not do what the personas expected of it. `missing_capability`: something they looked for is not there. `value_gap`: they judged what it offers not worth what it asks of them. `trust_gap`: something made them hesitate to trust it. `delight`: a moment that worked well. More kinds may be added; read one you do not know as unknown.
  - **`affected_personas`**: `(array of string) | null`
  - **`baseline_status`**: `string | null`

    Schema: `BaselineStatus`. Known values, open (more may be added): `new`, `persisting`, `resolved`, `unknown`.

    How this finding compares with the run named as the baseline. `new`: the baseline reached the same page and did not have it (for an issue, only one that is `reproducible`). `persisting`: the baseline has it too. `unknown`: nothing could say - no baseline was compared, the baseline never reached the page, or an issue the baseline does not have was not verified. `resolved` is never stamped on a finding of this run: a baseline issue this run no longer has is counted in `summary.issues_resolved_vs_baseline`. More values may be added; read one you do not know as `unknown`.
  - **`connector_refs`**: `object | null`

    **Additional properties:**

    `string`
  - **`location`**: `object | null`, schema: `Location`

    Where in the product the feedback is about: the page (`route`) and, when the personas pointed at one, the element in words (`element_description`).
    - **`element_description`**: `string | null`
    - **`route`**: `string | null`
  - **`persona_rationale`**: `string | null`
  - **`reached_sessions`**: `(array of string) | null`
  - **`solicited_by`**: `string | null`
  - **`suggested_change`**: `string | null`
  - **`title`**: `string | null`, maxLength: `120`
- **`run_id` (required)**: `string`
- **`status` (required)**: `string`

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

  Fields on each opportunity that are opinion. `description` is deliberately not among them.

**Example:**

```json
{
  "run_id": "",
  "status": "",
  "opportunities": [
    {
      "id": "",
      "kind": "unmet_expectation",
      "title": "",
      "description": "",
      "persona_rationale": "",
      "suggested_change": "",
      "affected_sessions": [
        ""
      ],
      "affected_personas": [
        ""
      ],
      "reached_sessions": [
        ""
      ],
      "consensus": 0,
      "solicited_by": "",
      "location": {
        "route": "",
        "element_description": ""
      },
      "evidence": {
        "step_refs": [
          {
            "session_id": "",
            "step_id": 1
          }
        ],
        "quotes": [
          ""
        ],
        "screenshots": [
          ""
        ]
      },
      "baseline_status": "new",
      "connector_refs": {
        "additionalProperty": ""
      }
    }
  ],
  "advisory": "",
  "subjective_fields": [
    ""
  ],
  "display": {
    "note": "",
    "min_consensus": 1,
    "min_affected_sessions": 1,
    "strong_consensus": 1,
    "shown": [
      ""
    ],
    "folded": [
      ""
    ]
  }
}
```

### 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

### BaselineStatus

**Type:** `string`

Known values, open (more may be added): `new`, `persisting`, `resolved`, `unknown`.

How this finding compares with the run named as the baseline. `new`: the baseline reached the same page and did not have it (for an issue, only one that is `reproducible`). `persisting`: the baseline has it too. `unknown`: nothing could say - no baseline was compared, the baseline never reached the page, or an issue the baseline does not have was not verified. `resolved` is never stamped on a finding of this run: a baseline issue this run no longer has is counted in `summary.issues_resolved_vs_baseline`. More values may be added; read one you do not know as `unknown`.

**Example:**

```json
"new"
```

**Example:**

```json
"persisting"
```

**Example:**

```json
"resolved"
```

**Example:**

```json
"unknown"
```

- `DisplaySplit` — shown above.

  The cohort floors, and which of the run's opportunities clear them.

  The split is the whole run's, so every surface - here, on GitHub, in the MCP answer - leads with the same
  record. Nothing is dropped: `folded` is a single-persona note, one click away.
- `Evidence` — shown above.
- `HTTPValidationError` — shown above.

### Kind

**Type:** `string`

Known values, open (more may be added): `unmet_expectation`, `missing_capability`, `value_gap`, `trust_gap`, `delight`.

What sort of feedback this is. `unmet_expectation`: the product did not do what the personas expected of it. `missing_capability`: something they looked for is not there. `value_gap`: they judged what it offers not worth what it asks of them. `trust_gap`: something made them hesitate to trust it. `delight`: a moment that worked well. More kinds may be added; read one you do not know as unknown.

**Example:**

```json
"unmet_expectation"
```

**Example:**

```json
"missing_capability"
```

**Example:**

```json
"value_gap"
```

**Example:**

```json
"trust_gap"
```

**Example:**

```json
"delight"
```

- `Location` — shown above.
- `Opportunity` — shown above.
- `OpportunityList` — shown above.
- `StepRef` — shown above.
- `ValidationError` — shown above.
