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

# Record a Simulator

> Capture, inspect, stop, and download MP4 screen recordings from iOS and Android sessions.

Screen recording is available for active iOS Simulator and Android Emulator
sessions through the CLI, TypeScript SDK, and REST API. A recording belongs to
its session and organization, and its download remains behind run.cloud
authentication.

Recordings contain the simulator display as MP4 video. They do not include
simulator audio. Only one recording can be active for a session at a time.

## CLI

Create a session and always arrange to release it:

<CodeGroup>
  ```bash iOS theme={null}
  SESSION_ID=$(runcloud ios create \
    --inactivity-timeout 3m \
    --hard-timeout 15m \
    --json | jq -r '.id')

  trap 'runcloud ios delete "$SESSION_ID" >/dev/null 2>&1 || true' EXIT
  ```

  ```bash Android theme={null}
  SESSION_ID=$(runcloud android create \
    --inactivity-timeout 3m \
    --hard-timeout 15m \
    --json | jq -r '.id')

  trap 'runcloud android delete "$SESSION_ID" >/dev/null 2>&1 || true' EXIT
  ```
</CodeGroup>

Start, inspect, stop, and download a recording. Replace `ios` with `android`
to use the same lifecycle on Android:

```bash theme={null}
RECORDING_ID=$(runcloud ios recording start "$SESSION_ID" \
  --idempotency-key "test-run-$CI_JOB_ID" \
  --json | jq -r '.id')

runcloud ios open-url "https://run.cloud" --id "$SESSION_ID" --json

runcloud ios recording status "$SESSION_ID" "$RECORDING_ID" --json
runcloud ios recording stop "$SESSION_ID" "$RECORDING_ID" --json
runcloud ios recording download "$SESSION_ID" "$RECORDING_ID" \
  --output ./simulator.mp4 \
  --json
```

The download command validates the MP4 container before writing it. Its JSON
result includes the absolute output path, byte size, content type, and SHA-256
digest, but never prints a storage credential or signed storage URL.

Use `recording list <session-id>` to inspect up to 100 retained recordings for
a session. `recording status` includes lifecycle events and any actionable
failure. `recording get` is an alias for `recording status`.

## Retry safely

Pass a stable `--idempotency-key` when starting a recording. Repeating `start`
with the same session and key returns the original recording instead of
starting another capture. The key may contain 1 to 200 printable characters.

Stopping a recording is also safe to repeat. A recording that is already
`ready` returns its current metadata. If finalization reports a retryable
failure, inspect `failure.action` and `failure.nextAttemptAt`, then poll
`recording status`; retained finalization may complete after the original stop
request.

Releasing a session attempts to finalize its active recording before returning
the simulator to the fleet. You can continue to inspect and download a ready
recording with the released session id. A recording is retained for seven days
from creation; `retentionExpiresAt` is the exact deadline.

## TypeScript SDK

Install `@run-cloud/sdk` and use the same methods on `cloud.ios` or
`cloud.android`:

```ts theme={null}
import { writeFile } from "node:fs/promises";
import { Client } from "@run-cloud/sdk";

const cloud = new Client();
const session = await cloud.android.create({
  inactivityTimeout: "3m",
  hardTimeout: "15m",
});

try {
  const recording = await cloud.android.startRecording(session.id, {
    idempotencyKey: `test-${process.env.CI_JOB_ID}`,
  });

  await cloud.android.openUrl(session.id, "https://run.cloud");

  const ready = await cloud.android.stopRecording(session.id, recording.id);
  if (ready.status !== "ready") {
    throw new Error(`Recording did not become ready: ${ready.status}`);
  }

  const mp4 = await cloud.android.downloadRecording(session.id, recording.id);
  await writeFile("simulator.mp4", mp4);
} finally {
  await cloud.android.delete(session.id);
}
```

Both platform clients expose:

| Method                               | Result                                                                 |
| ------------------------------------ | ---------------------------------------------------------------------- |
| `startRecording(id, options?)`       | Start or resume an idempotent capture and return `SimulatorRecording`. |
| `listRecordings(id)`                 | Return retained recordings newest first.                               |
| `getRecording(id, recordingId)`      | Return current metadata plus retained lifecycle events.                |
| `stopRecording(id, recordingId)`     | Stop and finalize the recording.                                       |
| `downloadRecording(id, recordingId)` | Return a validated MP4 as `Uint8Array`.                                |

`cloud.simulators` exposes the same methods with `platform: "ios" | "android"`
in the options object.

## Lifecycle and failures

`SimulatorRecording.status` is one of:

| Status                  | Meaning                                                       |
| ----------------------- | ------------------------------------------------------------- |
| `starting`              | Storage and native capture are being prepared.                |
| `recording`             | Screen capture is active.                                     |
| `finalizing`            | Capture is stopping and the MP4 is being verified.            |
| `ready`                 | The authenticated download is available.                      |
| `failed`                | The operation failed; inspect `failure` and lifecycle events. |
| `deleting` or `deleted` | Retention cleanup is in progress or complete.                 |

Ready metadata includes `contentType`, `byteSize`, `checksum`, `etag`,
`durationMs`, `readyAt`, `retentionExpiresAt`, and an authenticated API-relative
`downloadUrl`. Failures include `stage`, `code`, `message`, `action`,
`retryable`, and `nextAttemptAt`.

## REST API

Every route is scoped to the authenticated owner of the session:

```text theme={null}
POST /run-cloud/{platform}/{sessionId}/recordings
GET  /run-cloud/{platform}/{sessionId}/recordings
GET  /run-cloud/{platform}/{sessionId}/recordings/{recordingId}
POST /run-cloud/{platform}/{sessionId}/recordings/{recordingId}/stop
GET  /run-cloud/{platform}/{sessionId}/recordings/{recordingId}/download
```

Start with an idempotency key:

```bash theme={null}
curl --fail-with-body -sS \
  -X POST \
  -H "Authorization: Bearer $RUN_CLOUD_API_KEY" \
  -H "Idempotency-Key: test-run-1" \
  "https://api.run.cloud/run-cloud/ios/$SESSION_ID/recordings"
```

Download through the authenticated API. The API verifies the retained object
and redirects to a short-lived retrieval reference; credentials for backing
storage are never returned:

```bash theme={null}
curl --fail-with-body -sS -L \
  -H "Authorization: Bearer $RUN_CLOUD_API_KEY" \
  "https://api.run.cloud/run-cloud/ios/$SESSION_ID/recordings/$RECORDING_ID/download" \
  --output simulator.mp4
```
