# Step actual results

> The actual result of a test run step — rich text with inline images, written and read through the run case step endpoints.

While executing a test run, a member records an **actual result** for each step. It is rich text: send it as Markdown in `actualResult`, or as a document in `actualResultDocument` (see [Rich text fields](https://docs.probara.net/en/developers/api-reference/#rich-text-fields)). The text holds at most 20000 characters. Screenshots and other images go inline in the text: stage each one with `POST /api/v1/images:stage` and reference it as `![alt](attachment:<ULID>)`.

Files that are not images in the text, such as logs, traces and videos, are [result attachments](https://docs.probara.net/en/developers/api/result-attachments/). A result file can name a step with `stepIndex`.

Writes need the `attachments.execute` permission, which members, admins and owners hold. Viewers receive `403 forbidden`. Reads are always allowed. Writes follow the run state rules in [Test-run settings](https://docs.probara.net/en/developers/api/project-run-settings/): an `open` run accepts them, a `closed` run accepts them when `allowResultsInClosedRuns` resolves to `true` (the code default), and an `aborted` run always rejects them with `409`.

## Write a step's result

`PATCH /api/v1/runs/{runUlid}/cases/{runCaseUlid}/steps/{stepUlid}/result`

```json
{
  "actualResult": "Saw HTTP 500 instead of the dashboard:\n\n![Error page](attachment:01J...IMG)"
}
```

Send `actualResult` (Markdown) or `actualResultDocument` (the document), never both. `null` clears the result. Omitting both leaves the stored result unchanged.

**Errors:**

- `422 validation_failed` — both forms are sent, or the text breaks a rich-text rule (see [Rich text fields](https://docs.probara.net/en/developers/api-reference/#rich-text-fields)). An image that was never staged, or has expired, answers `details.code` `unknown_image`.
- `403 forbidden` — viewer role.
- `409` — the run state rules above.
- `413 storage_quota_exceeded` — the images would take the organization past its storage quota. Nothing is changed.

**Audit:** emits `run.case_step_actual_result_set` with the plain-text change of `actual_result`.

## Read

`GET /api/v1/runs/{runUlid}/cases/{runCaseUlid}` returns each step's `actualResult` (Markdown) and `actualResultDocument`. Both are `null` when the step has no result.

## Images

Stage an image with `POST /api/v1/images:stage` before the save that references it. Removing an image from the text and saving deletes it, unless another text or a stored revision still references it. Images in a result count toward the organization storage quota under `content_image`. A save that adds no image is never refused for storage.

## Run-case operations

- `POST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/retry` — resets the case (and its frozen step snapshots) to `untested` and recomputes the run aggregate. Emits `run.case_retried`.
- `DELETE /api/v1/runs/{runUlid}/cases/{runCaseUlid}` — removes the case with its steps and recomputes the run aggregate. Emits `run.case_removed`.

Both follow the closed-run rule above.

## Related

- [Result attachments](https://docs.probara.net/en/developers/api/result-attachments/) — files of any type attached to a whole result
- [Run result snapshots](https://docs.probara.net/en/developers/api/run-result-snapshots/) — what a completed result keeps
- [Test-run settings](https://docs.probara.net/en/developers/api/project-run-settings/) — `allowResultsInClosedRuns` and the rest of the run-settings vocabulary
- [Interactive v1 reference](https://docs.probara.net/en/developers/api-reference/v1/) — full schemas
