# List Playground runs

```http
GET /api/public/v1/competitions/lean-kernel-challenge/playground/runs
```

**Scope**: `playground.read`.

Returns a cursor-paginated list of the caller's Playground Runs. Runs are private, owner-scoped execution records; this endpoint never returns another account's history.

Existing Runs remain readable after unenrollment, Playground read-only mode, or Competition closure. These states prevent new work but do not remove retained Run history. Competition visibility and account ownership still apply.

## Query parameters

| Parameter | Type, requirement, and meaning |
| :--- | :--- |
| `problem` | **Optional string.** Exact, case-sensitive `problemId` stored on the Run. Historical IDs remain valid filters after a Problem leaves the current catalog; a well-formed value with no owned match returns an empty page. |
| `filter` | **Optional string.** History category: `all`, `active`, `accepted`, `rejected`, or `failed`. Defaults to `all`. |
| `cursor` | **Optional string.** Opaque continuation cursor returned by the preceding page with the same `problem` and `filter` values. |
| `limit` | **Optional integer.** Maximum number of Runs to return from `1` through `100`; defaults to `25`. |

Send each parameter at most once. Pass `nextCursor` unchanged; do not decode or construct it. A cursor is bound to the caller, endpoint, and normalized filter values. Start a new traversal when `problem` or `filter` changes.

## Filter semantics

`filter` is derived from the returned Run fields; it is not another Run status.

| Value | Included Runs |
| :--- | :--- |
| `all` | Every retained Run. |
| `active` | `status` is `pending` or `running`, including a scheduled automatic evaluator retry. |
| `accepted` | `status` is `done` and `finalConclusion` is `passed`. |
| `rejected` | `status` is `done` and `finalConclusion` is `failed`. |
| `failed` | `status` is `failed`, or `statusUncertain` is `true` because live evaluator status cannot currently be confirmed. |

Run lifecycle also includes terminal `cancelled`. Cancelled Runs appear in `all`; the endpoint does not define a separate cancelled-only filter. A list item is cancellable only when its authoritative `canCancel` value is `true`.

When `statusUncertain` is `true`, the response preserves the last confirmed `status` and progress fields. The Run appears under `failed` until the server confirms live status again; do not infer that evaluation ended.

## Ordering and pagination

Runs are ordered by `createdAt` descending, with a stable Run-ID tie-break. The cursor does not create a frozen snapshot. New Runs can appear before the current traversal, and an active Run can move between filter categories as its state changes. To refresh History or a filtered view, begin again without `cursor`.

An empty result is a successful page:

```json
{
  "ok": true,
  "data": {
    "items": [],
    "nextCursor": null
  }
}
```

## Response fields

| Field | Type and meaning |
| :--- | :--- |
| `items` | `PlaygroundRunSummary[]` — Current page in authoritative server order. Each item uses the shared [`PlaygroundRun`](./create-playground-run.md#response-fields) fields except `practicePolicy`, `practicePlan`, and `metrics`; retrieve one Run for its frozen contract and structured measurements. |
| `nextCursor` | `string \| null` — Opaque cursor for the next page, or `null` when traversal is complete. |

No total count is returned. `score` can provide a development-only display summary; it is never an official score. See [Get a Playground run](./get-playground-run.md) for the frozen practice policy, exact group plan, and structured measurements.

## Example request

```bash
export SAIR_API_BASE="https://api.sair.foundation/api/public/v1"

curl \
  "$SAIR_API_BASE/competitions/lean-kernel-challenge/playground/runs?problem=fib&filter=active&limit=25" \
  -H "Authorization: Bearer $SAIR_API_KEY"
```

## Example response

```json
{
  "ok": true,
  "data": {
    "items": [
      {
        "runId": "31",
        "solutionId": "17",
        "solutionRevision": 2,
        "solutionName": "baseline",
        "snapshotId": "24",
        "problemId": "fib",
        "status": "running",
        "statusUncertain": false,
        "canCancel": false,
        "historyCategory": "active",
        "finalConclusion": null,
        "verdict": null,
        "score": null,
        "reasonCode": null,
        "reasonMessage": null,
        "logExcerpt": null,
        "metrics": null,
        "phase": "judge",
        "progressText": "The judge is verifying your submission",
        "attempt": 1,
        "maxAttempts": 2,
        "pipelineVersion": "lean-kernel-2026-08-21.2",
        "progressObservedAt": "2026-09-01T08:00:12Z",
        "todos": [],
        "ruleVersion": "7a0abb4a80d635740a91aed1894f714dfbb82edd",
        "templateVersion": "7a0abb4a80d635740a91aed1894f714dfbb82edd",
        "toolchainVersion": "lean-4.33.1",
        "createdAt": "2026-09-01T08:00:00Z",
        "queuedAt": "2026-09-01T08:00:00Z",
        "startedAt": "2026-09-01T08:00:02Z"
      }
    ],
    "nextCursor": null
  }
}
```

Successful responses use `Content-Type: application/json`.

## Errors

| HTTP | Code | When |
| :---: | :--- | :--- |
| `400` | `INVALID_FILTER` | `filter` is not one of the documented lowercase values. |
| `400` | `INVALID_LIMIT` | `limit` is not an integer from `1` through `100`. |
| `400` | `INVALID_CURSOR` | `cursor` is malformed, altered, or belongs to another caller, endpoint, or filter traversal. |
| `404` | `NOT_FOUND` | The Competition does not exist or is not visible to the caller. |
| `422` | `RESOURCE_FIELD_INVALID` | `problem` is blank or contains surrounding whitespace, or a query parameter is unknown or repeated. |
| `500` | `RUN_STORE_FAILED` | The Run history could not be retrieved. Treat this as a service error, not an empty history. |
| `502` | `PLATFORM_UNAVAILABLE` | Competition visibility cannot be verified. |
| `503` | `RUN_STORE_BUSY` | Run state is being reconciled. Retry the same read. |

An evaluator status outage does not fail the whole page when the server has a last confirmed state. Affected items return `statusUncertain: true` instead.

See [Errors](../../../errors.md) for shared authentication, scope, and rate-limit errors.
