# Cancel a Playground run

```http
POST /api/public/v1/competitions/lean-kernel-challenge/playground/runs/{runId}/cancel
```

**Scope**: `playground.write`.

Cancels a Playground Run owned by the caller only while the evaluator still confirms that it is queued. Cancellation remains available for a Run admitted before the Playground became read-only or closed because it releases existing work rather than admitting new work.

## Path parameters

| Parameter | Type, requirement, and meaning |
| :--- | :--- |
| `runId` | **Required string.** Opaque Run ID returned by [Create a Playground run](./create-playground-run.md) or [List Playground runs](./list-playground-runs.md). |

## Request

The request has no body.

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

curl -X POST \
  "$SAIR_API_BASE/competitions/lean-kernel-challenge/playground/runs/$RUN_ID/cancel" \
  -H "Authorization: Bearer $SAIR_API_KEY"
```

## Response

Returns the shared [`PlaygroundRun`](./create-playground-run.md#response-fields) representation with its immutable source and evaluation-contract fields unchanged. The cancellation-specific result has:

| Field | Value and meaning |
| :--- | :--- |
| `status` | `"cancelled"` — Terminal Run state. |
| `statusUncertain` | `false` — Cancellation is reported as successful only after the evaluator confirms it. |
| `canCancel` | `false` — A terminal Run cannot be cancelled again as new work. |
| `historyCategory` | `"cancelled"`. Cancelled Runs remain visible under the unfiltered History view. |
| `finalConclusion`, `verdict`, `score`, `reasonCode`, `reasonMessage`, `logExcerpt`, `metrics` | `null` — Evaluation did not start and produced no participant result. |
| `cancelledAt` | ISO 8601 UTC time returned by the authoritative evaluator. |
| `cancellationReason` | Stable participant-facing explanation. |
| `dailyAllowance` | `"returned"` — The reserved daily Run allowance is released exactly once. |
| `evaluationResult` | `"not_produced"`. |
| `message` | Confirmation that the Run was cancelled and its allowance returned. |

The excerpt below focuses on fields changed by cancellation; the actual response also contains the shared immutable Solution, snapshot, rule, template, toolchain, practice-policy, and practice-plan fields.

```json
{
  "ok": true,
  "data": {
    "runId": "31",
    "problemId": "fib",
    "status": "cancelled",
    "statusUncertain": false,
    "canCancel": false,
    "historyCategory": "cancelled",
    "finalConclusion": null,
    "verdict": null,
    "score": null,
    "reasonCode": null,
    "reasonMessage": null,
    "logExcerpt": null,
    "metrics": null,
    "pipelineVersion": "lean-practice-v1",
    "progressObservedAt": null,
    "todos": [],
    "createdAt": "2026-09-04T12:00:00Z",
    "queuedAt": "2026-09-04T12:00:00Z",
    "cancelledAt": "2026-09-04T12:00:08Z",
    "cancellationReason": "Cancelled by the user before evaluation started",
    "dailyAllowance": "returned",
    "evaluationResult": "not_produced",
    "message": "Run cancelled. Your daily allowance has been returned."
  }
}
```

Cancellation succeeds only before execution starts. Repeating a successful cancellation is idempotent: it does not return the allowance twice and preserves the original `cancelledAt` value.

Clients must offer the action only when the latest create, list, or detail response has `canCancel: true`. Do not infer cancellability from `status: "pending"`: a Run with `statusUncertain: true` is not confirmed queued and has `canCancel: false`.

## Errors

| HTTP | Code | When |
| :---: | :--- | :--- |
| `403` | `API_KEY_SCOPE_DENIED` | A Public API key lacks `playground.write`. |
| `404` | `NOT_FOUND` | The Run does not exist or is not owned by the caller. |
| `409` | `RUN_ALREADY_STARTED` | Evaluation has already started. Refresh the Run. |
| `409` | `RUN_NOT_CANCELLABLE` | The Run is otherwise no longer in a cancellable queued state. Refresh the Run. |
| `500` | `RUN_STORE_FAILED` | The evaluator cancelled the Run but allowance settlement could not yet be confirmed. Retry the same cancellation. |
| `502` | `SERVICE_RESPONSE_INVALID` | The cancellation response could not be confirmed. Refresh the Run before retrying. |
| `502` | `SERVICE_UNAVAILABLE` | The evaluator could not process the cancellation. |

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