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

# Runs API

> Trigger flow execution, poll for results, retrieve run results, and terminate in-progress runs programmatically

All requests require an API key in the `X-API-Key` header. See [API Reference](/api-reference) for authentication details.

***

## List Devices

Discover the device and OS permutations that can be requested for cloud runs.

<Note>
  **GET** `/api/v1/devices` — Returns `200 OK`
</Note>

Optional query parameters are `platform` (`android` or `ios`) and `physical`
(`true` or `false`). Each model appears once with its supported `os_versions`.

```bash theme={null}
curl "https://backend.autosana.ai/api/v1/devices?physical=false" \
  -H "X-API-Key: YOUR_API_KEY"
```

Example response (abbreviated):

```json theme={null}
{
  "devices": [
    {
      "platform": "android",
      "physical": false,
      "model": "Pixel 10 Pro",
      "os_versions": ["17", "16"]
    },
    {
      "platform": "ios",
      "physical": false,
      "model": "iPhone 18 Pro",
      "os_versions": ["27.0"],
      "fast_os_versions": ["27.0"]
    },
    ...
  ]
}
```

Choose a model and one of its `os_versions`. For iOS simulators,
`fast_os_versions` identifies choices with an optimized startup path. Every
returned combination is supported.

***

## Run Flows

Trigger flow execution. Returns a `batch_id` to poll via [Run Status](#run-status) and a `batch_url` to view or share the entire batch in the dashboard.

<Note>
  **POST** `/api/v1/flows/run` — Returns `200 OK`
</Note>

### Request Body

<ParamField body="bundle_id" type="string">
  App bundle identifier (mobile). Required for mobile runs.
</ParamField>

<ParamField body="platform" type="string">
  `ios` or `android`. Required with `bundle_id`.
</ParamField>

<ParamField body="app_id" type="string">
  Web app identifier. Required for web runs. For mobile runs, optionally pass it with `bundle_id` and `platform` to pick the app that was [uploaded with that `app_id`](/api-ci#start-upload); omit it to run the app identified by bundle ID alone. Passing your bundle ID as `app_id` is the same as leaving it out.
</ParamField>

<ParamField body="app_build_id" type="string">
  Optional. Pin the run to a specific build (UUID) instead of the app's active build — useful for testing a particular CI/CD build. The build must belong to the resolved app, otherwise the request returns `404`. To find the build for a commit, see [List App Builds](/api-apps#list-app-builds).
</ParamField>

<ParamField body="suite_ids" type="string[]">
  Suite UUIDs to run. At least one of `suite_ids` or `flow_ids` is required.
</ParamField>

<ParamField body="parallelize_flows" type="boolean">
  Optional suite execution override. Explicit `true` or `false` takes precedence
  over each suite's saved **Run flows in parallel** setting. Omit it or send
  `null` to use the saved suite setting.
</ParamField>

<ParamField body="flow_ids" type="string[]">
  Flow UUIDs to run. At least one of `suite_ids`, `flow_ids`, or `labels` is required.

  When provided, suite auth instructions and suite-scoped variables are resolved automatically unless `resolve_suite_context` is set to `false`. See [Suite context for individual flows](#suite-context-for-individual-flows) below.
</ParamField>

<ParamField body="labels" type="string[]">
  Label **names** to run (e.g. `["smoke", "checkout"]`). Runs the union of the suites and flows carrying any of these labels; a flow already covered by a matched suite runs once. Returns `400` if nothing matches. See [Run by Label](/api-labels#run-by-label).
</ParamField>

<ParamField body="resolve_suite_context" type="boolean" default="true">
  When running individual flows via `flow_ids`, whether to look up the flow's suite and run auth instructions first. Defaults to `true`. Set to `false` to run the flow without suite auth or suite-scoped variables — useful for unauthenticated smoke tests or flows that handle login themselves.
</ParamField>

<ParamField body="suite_overrides" type="object">
  Optional. When a flow belongs to multiple suites, pass a JSON object mapping each ambiguous `flow_id` to the `suite_id` whose auth instructions and variables should be used, e.g. `{"<flow_id>": "<suite_id>"}`. Ignored when `resolve_suite_context` is `false`.
</ParamField>

<ParamField body="variables" type="string | object">
  Key-value variables to attach to the build being run (the pinned `app_build_id` if provided, otherwise the active build) and inject into flow instructions via `${env:KEY}`. Accepts a string (`"KEY1=VALUE1,KEY2=VALUE2"`) or a JSON object (`{"KEY1": "VALUE1"}`). See [Build Variables](/variables#build-variables).
</ParamField>

<ParamField body="web_browser" type="string">
  For web apps: which browser to run the flow in. One of:

  * `chrome` — real Google Chrome (default). Includes proprietary codecs (H.264, AAC) and Widevine DRM. Use this unless you have a specific reason to pick another browser.
  * `chromium` — open-source Chromium. No proprietary codecs, no DRM, deterministic and vendor-neutral.
  * `firefox` — Mozilla Firefox stable.
  * `edge` — Microsoft Edge stable.

  Aliases accepted: `Chrome` / `Google Chrome` → `chrome`; `msedge` / `Microsoft Edge` → `edge`. Ignored for mobile runs.
</ParamField>

<ParamField body="dependencies" type="array">
  Optional for web apps. Override the app's default Chrome extensions for this
  run. Each entry can be an extension app UUID or an object that pins an exact
  build:

  ```json theme={null}
  [
    "extension-app-uuid",
    {
      "app_id": "another-extension-app-uuid",
      "app_build_id": "extension-build-uuid"
    }
  ]
  ```

  Omit this field to inherit the web app's attached defaults. Pass `[]` to run
  without extensions. When extensions load, `chrome`, `edge`, and `chromium`
  run in Chromium; `firefox` is rejected because Chrome extensions are not
  supported there.
</ParamField>

<ParamField body="device" type="object">
  Optional for mobile apps. Selects the execution target and, when specified,
  the exact supported device permutation. Fields are `physical` (defaults to
  `false`), `model` (for example,
  `"Pixel 10 Pro"`), and `os_version` (for example, `"17"`). Set `model` or
  `os_version` to `"latest"` to explicitly request rolling resolution for that
  field. This lets you pin one while keeping the other current. Use
  [List Devices](#list-devices) to discover valid combinations. Omit it to use
  the recommended default. A request with no supported match fails fast with a
  clear error. For an iOS simulator, use `physical: false` with a listed iPhone
  or iPad model and iOS version. For a real iOS device, use `physical: true`
  and an `.ipa` build.
</ParamField>

<ParamField body="devices" type="object[]">
  Use instead of `device` to choose each device of a two-device flow. Pass
  exactly two entries in Device 1, Device 2 order. A run can mix one-device and
  two-device flows: one-device flows use the first entry, and a two-device flow
  run with `device` (or with neither field) uses that selection for both
  devices. Both entries must select virtual devices
  (`physical: false`) or both must select real devices (`physical: true`). Model
  and OS can be exact, `"latest"`, or omitted for rolling resolution. Both
  devices use the run's app build and platform.

  ```json theme={null}
  [
    { "physical": true, "model": "iPhone 17 Pro", "os_version": "26" },
    { "physical": true, "model": "iPhone 16 Pro", "os_version": "18" }
  ]
  ```

  See [Multi-Device Testing](/multi-device-testing) for a complete setup and
  execution guide.
</ParamField>

For example, keep the model and OS current on every run:

```json theme={null}
{
  "device": {
    "physical": false,
    "model": "latest",
    "os_version": "latest"
  }
}
```

### Suite context for individual flows

When you trigger a single flow via `flow_ids`, Autosana resolves the suite it belongs to and runs that suite's **Auth Instructions** first — the same behavior as running the flow manually from the dashboard.

```bash theme={null}
# Runs auth instructions first, then the flow (default)
curl -X POST https://backend.autosana.ai/api/v1/flows/run \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "your-app-id",
    "flow_ids": ["660e8400-e29b-41d4-a716-446655440001"]
  }'

# Skip suite auth — run only the flow instructions
curl -X POST https://backend.autosana.ai/api/v1/flows/run \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "your-app-id",
    "flow_ids": ["660e8400-e29b-41d4-a716-446655440001"],
    "resolve_suite_context": false
  }'
```

<Tip>
  If a flow belongs to multiple suites, the request returns `400` unless you pass `suite_overrides` mapping that flow to one of its suite UUIDs, e.g. `{"660e8400-e29b-41d4-a716-446655440001": "550e8400-e29b-41d4-a716-446655440000"}`.
</Tip>

### Example Request

```bash theme={null}
curl -X POST https://backend.autosana.ai/api/v1/flows/run \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "bundle_id": "com.company.app",
    "platform": "android",
    "suite_ids": ["550e8400-e29b-41d4-a716-446655440000"],
    "variables": "PR_NUMBER=42,BRANCH=feature/login"
  }'
```

### Example: Run with Chrome extensions

```bash theme={null}
curl -X POST https://backend.autosana.ai/api/v1/flows/run \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "your-web-app-uuid",
    "flow_ids": ["660e8400-e29b-41d4-a716-446655440001"],
    "dependencies": [
      "extension-app-uuid",
      {
        "app_id": "wallet-extension-app-uuid",
        "app_build_id": "wallet-extension-build-uuid"
      }
    ]
  }'
```

All dependency apps and pinned builds must belong to the same organization as
the primary web app. Duplicate app IDs are loaded once; conflicting build pins
return `400`.

### Example Response

```json theme={null}
{
  "status": "success",
  "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "batch_url": "https://autosana.ai/runs/batches/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "flow_group_run_ids": ["f1a2b3c4-d5e6-7890-abcd-ef1234567890"],
  "flow_run_count": 5
}
```

***

## Run Status

Poll execution status for a batch triggered by [Run Flows](#run-flows).

<Note>
  **GET** `/api/v1/runs/status` — Returns `200 OK`
</Note>

### Query Parameters

<ParamField query="batch_id" type="string" required>
  The `batch_id` from `/api/v1/flows/run`.
</ParamField>

### Example Request

```bash theme={null}
curl -X GET "https://backend.autosana.ai/api/v1/runs/status?batch_id=BATCH_ID" \
  -H "X-API-Key: YOUR_API_KEY"
```

### Example Response

```json theme={null}
{
  "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "batch_url": "https://autosana.ai/runs/batches/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "is_complete": true,
  "timestamp": "2025-01-31T12:34:56.789Z",
  "app": {
    "id": "com.company.app",
    "bundle_id": "com.company.app",
    "name": "My App",
    "platform": "android",
    "url": "https://storage.example.com/builds/app.apk"
  },
  "git": {
    "commit_sha": "abc123",
    "branch": "main"
  },
  "summary": {
    "total_groups": 2,
    "passed_groups": 1,
    "failed_groups": 1,
    "total_flows": 10,
    "passed_flows": 8,
    "failed_flows": 2,
    "error_flows": 0,
    "terminated_flows": 0,
    "skipped_flows": 0
  },
  "run_groups": [
    {
      "name": "Login Suite",
      "status": "passed",
      "url": "https://autosana.ai/runs/groups/group-uuid-1",
      "source": "ci",
      "actor": { "id": "770e8400-e29b-41d4-a716-446655440077", "name": "CI Key", "type": "api_key" },
      "dependency_app_build_ids": ["extension-build-uuid"],
      "runs": [
        {
          "id": "run-uuid-1",
          "name": "Login with valid credentials",
          "status": "passed",
          "url": "https://autosana.ai/runs/flow/run-uuid-1",
          "summary": "Successfully logged in and verified the dashboard loaded."
        }
      ]
    },
    {
      "name": "Checkout Suite",
      "status": "failed",
      "url": "https://autosana.ai/runs/groups/group-uuid-2",
      "source": "schedule",
      "actor": { "id": "f8d8fb05-6292-4daa-9bf4-b0cc3b40a366", "name": "Autosana", "type": "system" },
      "dependency_app_build_ids": [],
      "runs": [
        {
          "id": "run-uuid-2",
          "name": "Checkout with expired card",
          "status": "failed",
          "url": "https://autosana.ai/runs/flow/run-uuid-2"
        }
      ]
    }
  ]
}
```

`app.id` is the app's ID: the bundle ID for a mobile app, or its custom `app_id` when it was [uploaded with one](/api-ci#start-upload). `app.bundle_id` is always the bundle ID, so use it if you match on bundle ID. For a web app both hold the app ID.

Each run group also includes:

* `status`: common values include `creating`, `pending`, `queued`, `running`, `reviewing`, `verifying`, `terminating`, `terminated`, `passed`, `failed`, `error`, and `skipped`. Treat this list as non-exhaustive. Poll until a terminal status rather than switching on every value.
* `source`: how it was triggered (`ci`, `dashboard`, `schedule`, or `mcp`)
* `actor`: who triggered it, as `{ id, name, type }`, where `type` is `user`, `system`, or `api_key`
* `dependency_app_build_ids`: exact Chrome extension build UUIDs loaded for the group

Runs triggered with an API key (this endpoint, MCP, CI) are attributed to the key itself: `type` is `api_key`, `id` is the key's ID, and `name` is the key's name from your dashboard. `source` and `actor` may be `null` for older runs.

***

## Get Run Results

Get full details of a run, including metadata, summary, the raw run recording URL, and all actions with screenshot URLs. Use the `id` from individual runs in the [Run Status](#run-status) response.

<Note>
  **GET** `/api/v1/runs/{run_id}` — Returns `200 OK`
</Note>

### Path Parameters

<ParamField path="run_id" type="string" required>
  UUID of the run. Available in the `runs[].id` field from [Run Status](#run-status).
</ParamField>

### Example Request

```bash theme={null}
curl -X GET "https://backend.autosana.ai/api/v1/runs/run-uuid-1" \
  -H "X-API-Key: YOUR_API_KEY"
```

### Example Response

```json theme={null}
{
  "run_id": "run-uuid-1",
  "flow_name": "Login with valid credentials",
  "platform": "web",
  "status": "passed",
  "started_at": "2025-01-31T12:34:50.000000+00:00",
  "completed_at": "2025-01-31T12:35:01.000000+00:00",
  "source": "ci",
  "actor": { "id": "770e8400-e29b-41d4-a716-446655440077", "name": "CI Key", "type": "api_key" },
  "dependency_app_build_ids": ["extension-build-uuid"],
  "summary": "Successfully logged in and verified the dashboard loaded.",
  "issues": [
    {
      "type": "ux",
      "severity": "minor",
      "title": "No loading indicator on login",
      "description": "After tapping 'Sign In', there is no visual loading state before the dashboard appears.",
      "actions": [4,5]
    }
  ],
  "url": "https://autosana.ai/runs/flow/run-uuid-1",
  "recording_url": "https://storage.example.com/recordings/run-uuid-1.mp4",
  "performance_data_url": "https://storage.example.com/perf/run-uuid-1.json",
  "device_log_url": "https://storage.example.com/logs/run-uuid-1/device.log?token=...",
  "network_log_url": "https://storage.example.com/logs/run-uuid-1/network.jsonl?token=...",
  "actions": [
    {
      "id": "action-uuid-1",
      "position": 1,
      "type": "tap",
      "status": "passed",
      "description": "Tap the 'Sign In' button",
      "value": null,
      "screenshot_url": "https://storage-path/screenshot1.png",
      "annotated_screenshot_url": "https://example.com/screenshot1_annotated.png",
      "executed_at": "2025-01-31T12:34:56.789000+00:00"
    },
    {
      "id": "action-uuid-2",
      "position": 2,
      "type": "send_keys",
      "status": "passed",
      "description": "Type email into the email field",
      "value": "user@example.com",
      "screenshot_url": "https://storage-path/screenshot2.png",
      "annotated_screenshot_url": "https://example.com/screenshot2_annotated.png",
      "executed_at": "2025-01-31T12:34:58.789000+00:00"
    },
    {
      "id": "action-uuid-3",
      "position": 3,
      "type": "pass",
      "status": "passed",
      "description": "Login completed successfully — dashboard is visible",
      "value": null,
      "screenshot_url": "https://storage-path/screenshot3.png",
      "annotated_screenshot_url": null,
      "executed_at": "2025-01-31T12:35:00.789000+00:00"
    }
  ]
}
```

### Artifact URLs

<ResponseField name="dependency_app_build_ids" type="string[]">
  Exact Chrome extension build UUIDs loaded for this run.
</ResponseField>

<ResponseField name="devices" type="object[]">
  Per-device metadata and artifact URLs for a two-device run, ordered by
  `device_index`. Each entry includes model and OS metadata and that
  device's recording, performance, device-log, and network-log URLs.
  Single-device runs return an empty array and continue to use the top-level
  artifact fields below.
</ResponseField>

<ResponseField name="recording_url" type="string | null">
  Public URL for the raw, unannotated MP4 recording of the run. `null` when the run did not produce a recording.
</ResponseField>

<ResponseField name="performance_data_url" type="string | null">
  Time-series performance metrics (memory, CPU, web vitals) as JSON.
</ResponseField>

<ResponseField name="device_log_url" type="string | null">
  Device log (Android `logcat` / iOS syslog) or browser console log (web), as plain text or JSONL.
</ResponseField>

<ResponseField name="network_log_url" type="string | null">
  HTTP requests captured during the run, as JSONL — one entry per request with `method`, `url`, `status`, `resource_type`, `duration_ms`, `response_size`, etc. Failed requests (DNS, abort, CORS) appear with `status: 0` and an `error` string.

  Produced for **web** and **iOS** runs (iOS from the cloud simulator; apps that pin their TLS certificate aren't captured). `null` when no traffic was captured. See [Network Traffic](/network-traffic).
</ResponseField>

Any of these may be `null` if the artifact wasn't produced for that run.

Actions from a two-device run include `device_index` (`1` or `2`).

***

## Terminate Run

Terminate an in-progress flow group run. `run_id` may be a flow group run ID from [Run Flows](#run-flows) (`flow_group_run_ids`) or a flow run ID from [Run Status](#run-status) / [Get Run Results](#get-run-results).

<Warning>
  Passing a flow run ID terminates the **entire parent group**, including every sibling flow in that suite or batch, not just the one run.
</Warning>

<Note>
  **POST** `/api/v1/runs/terminate` returns `200 OK`
</Note>

### Request Body

<ParamField body="run_id" type="string" required>
  UUID of a flow group run or a flow run.
</ParamField>

Pending runs are terminated immediately. Queued or running groups are marked `terminating`. Already-terminal runs return `action: "none"`. Unknown or other-organization IDs return `404`.

To watch termination finish, reuse the `batch_id` from [Run Flows](#run-flows) and poll [Run Status](#run-status) until that group's `status` is terminal (`terminated`, `skipped`, `passed`, `failed`, or `error`). The terminate response does not include `batch_id`. You can also poll [Get Run Results](#get-run-results) with the original flow run ID until that flow reaches any terminal status. Children that never started may be `skipped` rather than `terminated`.

### Example Request

```bash theme={null}
curl -X POST https://backend.autosana.ai/api/v1/runs/terminate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "run_id": "660e8400-e29b-41d4-a716-446655440001"
  }'
```

This example passes a **flow run** ID. The response echoes that ID in `run_id` and the resolved parent group in `flow_group_run_id`.

### Example Response

```json theme={null}
{
  "run_id": "660e8400-e29b-41d4-a716-446655440001",
  "flow_group_run_id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
  "status": "terminating",
  "message": "Marked for termination (worker will finalize)",
  "action": "terminating"
}
```

<ResponseField name="run_id" type="string">
  The `run_id` you sent (flow group run or flow run).
</ResponseField>

<ResponseField name="flow_group_run_id" type="string">
  The parent group that was actually terminated. Same as `run_id` when you passed a group ID.
</ResponseField>

<ResponseField name="status" type="string">
  Current group status after the request. Typically `terminated` or `terminating`. When `action` is `none`, this is the group's existing terminal status (`passed`, `failed`, `error`, `terminated`, or `skipped`).
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable outcome, for example `"Marked for termination (worker will finalize)"` or `"Already in terminal state"`.
</ResponseField>

<ResponseField name="action" type="string">
  `terminated` (pending run stopped immediately), `terminating` (worker will finalize), or `none` (already terminal).
</ResponseField>

***

## Polling

Poll `/api/v1/runs/status` every 10-15 seconds until `is_complete` is `true`.

```bash theme={null}
while true; do
  RESPONSE=$(curl -s "https://backend.autosana.ai/api/v1/runs/status?batch_id=$BATCH_ID" \
    -H "X-API-Key: YOUR_API_KEY")

  if [ "$(echo "$RESPONSE" | jq -r '.is_complete')" = "true" ]; then
    echo "$RESPONSE" | jq '.summary'
    break
  fi

  sleep 15
done
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.