These endpoints let a CI job trigger a suite or test run and poll it to completion. Authentication, error shapes and rate limits are covered in the REST API overview. A group is called a suite in the API.
Headless endpoints used by the VibeView CLI and workflow template. These allocate a device, create a session, run the test or suite, and clean the session up. See CI Testing for the end-to-end flow.
The session a CI run creates belongs to your organization like any other: it shows in your session list while it runs, and you can stop it with Stop Session (or vibeview stop <session-id>).
Refused before a device is allocated. The run endpoints check that the run can start before they take a device for it:
403when AI can’t run — for example you are out of usage credit or the model isn’t available. The responsedetailcarries the samemessage(andremedy) as the dashboard’s run endpoints. Runs inreplaymode make no AI calls and are not checked.400whenmodeisreplayand the test case has no recording (for a suite: when none of its cases has one). Replay mode never falls back to AI; run the case inhybridoraimode instead.403when your organization’s monthly streaming minutes are used up and overage is disabled, or your usage credit is too low to cover overage.detailis the same message the dashboard shows when you start a session there.
A CI run’s session counts toward your plan’s concurrent sessions like any other. When every concurrent session your plan allows is already in use, the run is not refused: it is created with status: "queued", the poll’s queue.reason is plan_limit, and it starts as soon as one of your sessions ends. A run waits up to 30 minutes; if every session is still in use by then, it ends error and never starts. If your streaming minutes run out while a run is still waiting, it ends error with the same message when its turn comes.
Run Suite (CI)
POST /api/v1/ci/run-suite
Auth: Bearer token required (developer role or above).
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
suite_id | string | Yes | public_id of the suite |
commit_sha | string | No | Git commit SHA for GitHub status checks |
model | string | No | Model override |
provider | string | No | Provider override |
mode | string | No | hybrid, ai, or replay |
device_category | string | No | phone, tablet, or tv. Defaults to tv for tvOS and Android TV, phone otherwise. A category never allocates a foldable; pin one with device_id. |
device_id | string | No | Pin the run to a specific device (from List Devices). Mutually exclusive with device_category. |
metadata | object | No | Arbitrary JSON object passed through unchanged to webhook payloads for this run (e.g. PR number, branch). |
build_id | int | No | Specific AppBuild id. Defaults to the latest build of the app the run installs (the suite’s app, or the one platform/app_id names). Returns 400 if no app is attached to the suite, 404 if your organization has no such build, 403 if the build belongs to a different app. Both errors list the app’s newest builds. 410 (build_expired) if the build has expired (see Build retention). |
platform | string | No | Run the suite on another platform: ios, android, tvos or androidtv. The run installs the suite’s app on that platform: the app linked to it as the same product, else the app with the same bundle id / package name. See Cross-platform runs. |
app_id | string | No | Run the suite against this app (public_id) instead of the suite’s own, for example when the other platform’s build has another bundle id. Pass app_id or platform, not both. |
environment | string | No | Name or id of one of the suite’s app’s environments. Its test data values replace the app’s for this run. Omitted: the app’s own values. Returns 404 if the app has no such environment; the message lists the ones it has. |
retry_failed | boolean | No | true retries a failed test once, on the same device after a fresh start. Defaults to false: retries are off unless you ask for them. A quarantined test and a test that failed because its session ended are never retried. See Retries. |
Response:
{"run_id": "suite-run-public-id", "session_id": "…", "status": "pending", "app_id": "atlas-android", "platform": "android", "build_warning": null}
status is queued when no device is free yet, or when your plan’s concurrent sessions are all in use. app_id and platform name the app the run installs. build_warning is set when that build needs a Metro dev server (see Debug builds that need Metro). A suite whose every case has no steps is refused with 400 before a device is taken.
Run Test (CI)
POST /api/v1/ci/run-test
Auth: Bearer token required (developer role or above).
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
test_id | string | Yes | public_id of the test case |
commit_sha | string | No | Git commit SHA for GitHub status checks |
model | string | No | Model override |
provider | string | No | Provider override |
mode | string | No | hybrid, ai, or replay |
device_category | string | No | phone, tablet, or tv. Defaults to tv for tvOS and Android TV, phone otherwise. A category never allocates a foldable; pin one with device_id. |
device_id | string | No | Pin the run to a specific device (from List Devices). Mutually exclusive with device_category. |
metadata | object | No | Arbitrary JSON object passed through unchanged to webhook payloads for this run (e.g. PR number, branch). |
build_id | int | No | Specific AppBuild id. Defaults to the latest build of the app the run installs (the test’s app, or the one platform/app_id names). Returns 400 if no app is attached to the test case, 404 if your organization has no such build, 403 if the build belongs to a different app. Both errors list the app’s newest builds. 410 (build_expired) if the build has expired (see Build retention). |
platform | string | No | Run the test on another platform: ios, android, tvos or androidtv. The run installs the test’s app on that platform: the app linked to it as the same product, else the app with the same bundle id / package name. See Cross-platform runs. |
app_id | string | No | Run the test against this app (public_id) instead of its own, for example when the other platform’s build has another bundle id. Pass app_id or platform, not both. |
environment | string | No | Name or id of one of the test’s app’s environments. Its test data values replace the app’s for this run. Omitted: the app’s own values. Returns 404 if the app has no such environment; the message lists the ones it has. |
retry_failed | boolean | No | true retries a failed test once, on the same device after a fresh start. Defaults to false: retries are off unless you ask for them. A quarantined test and a test that failed because its session ended are never retried. See Retries. |
Response:
{"run_id": "test-run-public-id", "session_id": "…", "device_name": "Pixel 9", "status": "pending", "app_id": "atlas-android", "platform": "android", "build_warning": null, "warnings": []}
status is queued (and device_name absent) when no device is free yet, or when your plan’s concurrent sessions are all in use. A test case with no steps is refused with 400 before a device is taken.
warnings lists each {{key}} the test’s steps (its setup steps included) use that this run has no value for, on the platform it runs on and with its environment: code (missing_test_data), key and message, for example “Steps use {{pin}}, which has no value for Atlas on Android.” It is [] when every key has a value. The run still starts: a step that only mentions the key can pass, a step that tries to type it fails, and a run whose recorded typing would type it fails before its first step. See When a key has no value. The CLI prints each warning.
Debug builds that need Metro
A debug build of a React Native app loads its JavaScript from a Metro dev server. A test run has none (a vibeview dev session’s Metro connection belongs to that session only), so on such a build the app shows an error screen and the run will almost certainly fail at launch. It is the same build the dashboard marks needs Metro.
Without build_id, a run uses the newest build that doesn’t need Metro. (The dashboard and the Tests API refuse a run when every build needs Metro; the CI endpoints start it with the warning below.) When the build the run installs needs Metro anyway — you pinned one with build_id, or every build of the app needs Metro — the start response carries build_warning, a sentence naming the build and what to do: upload a release build, or pin one. Otherwise build_warning is null. The run still starts: a warning, not a refusal, so a pinned build is always honored. The CLI prints the warning and includes it in its JSON and JUnit output.
Cross-platform runs
A recording replays on another platform of the same form factor: iOS and Android phones and tablets, or Apple TV and Android TV. Pass platform to run a suite or test there without copying it: VibeView uses the app on that platform that you linked to the test’s app as the same product (see Same app on other platforms); for an unlinked app, or a platform the product lacks, it uses the app with the same bundle id (iOS) or package name (Android). It installs that app and runs the test against it. Results stay on the same test, and a run with commit_sha posts its commit status under the platform it ran on.
404— no app on that platform has that bundle id. The message lists your apps on that platform; pass one asapp_id, or upload that platform’s build first.409— more than one app on that platform has that bundle id. The message lists them; pass the one you want asapp_id.400— the platform or app is for the other form factor (a phone test on Apple TV or Android TV, or the reverse). This is checked first, so a phone test asked to run ontvosis refused with that reason rather than a list of TV apps. Also400when bothapp_idandplatformare passed: an app has one platform, so pass one or the other.
Poll Run Status (CI)
Poll a CI-triggered run for completion. Works for both suite runs and individual test runs — pass the run_id returned by Run Suite (CI) or Run Test (CI).
GET /api/v1/ci/runs/{run_id}
Auth: Bearer token required (developer role or above).
Response: A type: "suite_run" or type: "test_run" payload with status, run_url, timing, error details, per-step/per-case results on terminal runs, live progress events while running, and a queue block while queued. See CI Testing for full payload examples.
A test_run payload also has warnings: the keys its steps used with no value, once they ran (code, key, steps, message; [] otherwise), and each entry of steps has missing_test_data, that step’s keys (null when none, and on older runs), and missing_test_data_not_typed, those whose typing was refused.
Retries and quarantine. When a failed test is retried, the poll reports the retry’s result as the test’s result, and keeps the first attempt under first_attempt (run_id, status, error_message, url). A single test’s payload also carries retry_run_id, the retry’s id. While a failed first attempt may still be retried, status stays running, so the poll never reports a failure inside the retry window. In a suite run’s payload, a retried test appears once, with retry_of (the first attempt’s id) and first_attempt; each entry has quarantined (true for a quarantined test), and quarantined_failures counts the quarantined tests that failed. Those failures don’t fail the suite run. A single test’s payload also has quarantined; a quarantined test that failed keeps status: "failed", and the CLI exits 0 for it, as for a suite run whose only failures are quarantined. A run that was blocked for a reason that isn’t your app reports error, and GitHub receives an error status for it.
Not checked and not run. A run in which no step was checked (for example a test of Verify steps only, run in replay mode) is neither a pass nor a failure. The poll reports it with status: "passed", so scripts and earlier CLI versions treat it as finished and not failing, and adds not_checked: true; its error_message says why. Every test run in a payload (a single test’s payload, each test_runs[] entry, each case_runs[] entry) carries not_checked and not_run, both false unless they apply. A suite run in which every test was not checked also reports status: "passed" with not_checked: true. A test that didn’t run because an earlier test failed under Stop on first failure keeps status: "cancelled" and has not_run: true. GitHub receives a success status worded “not checked” for a not-checked run. See Not checked and not run.
List Suites (CI)
GET /api/v1/ci/list-suites
Auth: Bearer token required (developer role or above).
Query parameters:
| Parameter | Type | Description |
|---|---|---|
tags | string | Comma-separated tag names to filter by (e.g. ?tags=smoke,profiles) |
match | string | any (default) — suite has at least one tag; all — suite has every tag |
Response: Array of suite summaries.
[
{
"id": "suite_abc123",
"name": "Login Flow",
"platform": "ios",
"case_count": 5,
"description": "Covers email/password and SSO sign-in.",
"tags": [{"name": "smoke", "display_name": "smoke"}]
}
]
description is a string or null. tags is always an array (possibly empty); each tag has a normalized name and a display_name.
Run Multiple Suites by Tag (CI)
POST /api/v1/ci/run-suites
Auth: Bearer token required (developer role or above).
Runs every suite matching the tag filter, sequentially. Each matched suite gets its own device session and SuiteRun.
The whole batch is refused with 403 before any suite starts when your streaming minutes are used up and overage is disabled (same message as above). A suite whose turn comes after the minutes ran out ends error with that message; a suite whose turn comes while your plan’s concurrent sessions are all in use waits in the queue.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
tags | string[] | Yes | Tags to match (1–20). Each must satisfy ^[a-z0-9_-]{1,50}$. |
match | string | No | any (default) or all. |
commit_sha | string | No | Propagated to every child SuiteRun. |
model | string | No | AI model. |
provider | string | No | AI provider. |
mode | string | No | AI use: hybrid (Smart), ai (AI only), replay (No AI). |
device_category | string | No | phone, tablet, or tv. Defaults to tv for tvOS and Android TV, phone otherwise. A category never allocates a foldable; pin one with device_id. |
device_id | string | No | Pin every run to a specific device (from List Devices). Mutually exclusive with device_category. |
metadata | object | No | Arbitrary JSON object passed through unchanged to webhook payloads for these runs (e.g. PR number, branch). |
build_id | int | No | AppBuild id. Validated against every matched suite’s app upfront — 400 if any suite’s app doesn’t match. |
environment | string | No | Environment name or id. Each suite runs with its own app’s environment of that name. 404 before any run starts if a matched suite’s app has no such environment; the message names those suites. |
retry_failed | boolean | No | true retries a failed test once, on the same device after a fresh start. Defaults to false: retries are off unless you ask for them. A quarantined test and a test that failed because its session ended are never retried. See Retries. |
Response: 200 OK
{
"aggregate_id": "agg_abc123",
"matched_suite_ids": ["suite_a", "suite_b"],
"matched_suites": [
{"id": "suite_a", "name": "Login", "build_warning": null},
{"id": "suite_b", "name": "Checkout", "build_warning": null}
],
"excluded_suites": [],
"status_url": "/api/v1/ci/multi-suite-runs/agg_abc123"
}
Each matched suite’s build_warning is set when the build its run installs needs a Metro dev server, as on Run Test (CI).
Errors:
404— no suites matched the tag filter. The message lists the tags your groups have.400—build_idis not valid for one or more matched suites (response body lists the offending suite IDs)410—build_idis a build of a matched suite’s app that has expired:detail.errorisbuild_expired, withmessageandnewer_build_id(see Build retention). No suite starts.422— invalid tag name (e.g. uppercase or special chars)
Get Multi-Suite Run Status
GET /api/v1/ci/multi-suite-runs/{public_id}
Auth: Bearer token required (developer role or above).
Returns the aggregate status and per-suite child summaries. Suitable for polling.
Response: 200 OK
{
"id": "agg_abc123",
"status": "running",
"visual_regression_status": "warn",
"suite_runs": [
{
"suite_id": "suite_a",
"suite_name": "Profiles smoke",
"run_id": "run_xyz",
"status": "passed",
"passed": 5,
"failed": 0,
"visual_regression_status": "warn",
"case_runs": [
{
"case_name": "Open profile",
"run_id": "run_pq",
"status": "passed",
"visual_regression_status": "warn",
"duration_ms": 4200,
"url": "https://vibeview.io/tests/runs/run_pq"
}
]
}
],
"started_at": "2026-04-28T12:00:00",
"completed_at": null,
"commit_sha": "abc123"
}
status is one of pending, running, passed, failed, cancelled, error. Aggregate precedence: running if any child is in-flight; else failed > error > cancelled > passed. An aggregate, a suite or a case in which nothing was checked reports passed with not_checked: true; each suite also counts its tests not checked in not_checked_tests, apart from passed.
visual_regression_status (present at the aggregate, suite, and case levels) is one of ok, warn, fail, or null when no visual comparison ran. Suite and aggregate values roll up their children by max severity (fail > warn > ok > null). A run can be passed while carrying a warn/fail verdict — CI surfaces (e.g. the GitHub PR comment) render this as a yellow warning rather than a failure, matching the dashboard.
A child suite run that is waiting for a device (status: "queued") also carries a queue block with entry_id, position, specific_device_id, reason: why it waits (devices_busy, devices_updating or plan_limit), or null when VibeView can’t tell, and priority. priority is high on paid plans and low on Free. When every device is busy, paid plans’ requests start first, so a Free request’s position can go up.
If a multi-suite run is interrupted on VibeView’s side before it can report a result, VibeView closes it about ten minutes after its last suite finishes: the aggregate becomes failed, cancelled or error from its suites’ results, and error when they all passed, because suites after the interruption never ran. A run that never got as far as its first suite becomes error.
Errors:
404— multi-suite run not found in user’s org
Each entry of suite_runs also carries quarantined_failures, and a retried case carries retry_of and first_attempt, as in Poll Run Status.
Cancel Multi-Suite Run
DELETE /api/v1/ci/multi-suite-runs/{public_id}
Auth: Bearer token required (developer role or above).
Cancels the run. The aggregate becomes cancelled at once, suites that haven’t started are skipped, and a suite still waiting for a device leaves the queue, so it never starts or uses usage credit. A suite that is running stops before its next test case. The CLI calls this when run-suites is interrupted or times out.
Response: 200 OK with {"status": "cancellation_requested"}, or {"status": "already_terminal", "aggregate_status": "<status>"} when the run had already finished.
Errors:
404— multi-suite run not found in user’s org
DELETE /api/v1/ci/multi-suite-runs/{id} and DELETE /api/v1/ci/queue/{id} still work. POST /api/v1/tests/runs/{id}/cancel cancels any run.