VibeView API Reference
VibeView exposes a REST API for managing simulator sessions, apps, tests, organizations, and AI-powered test execution. All endpoints are served under the /api/v1/ prefix.
Base URL: https://<your-vibeview-host>/api/v1
Authentication
All endpoints (except registration, login, and the email-verification and password-reset endpoints) require a valid Bearer token in the Authorization header.
Authorization: Bearer <token>
Tokens are obtained via the login or registration endpoints. You can also create long-lived API tokens in your organization settings (see API Tokens).
Auth
Register
Create a new account. For self-serve signup, a new organization is created and named from organization_name. To join an existing organization instead, pass the invite_token from an invitation email — the account is created as a member of the inviting organization, and because the invite is bound to your email address, no separate email verification is needed.
POST /api/v1/auth/register
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email address |
password | string | Yes | Account password (min 8 characters) |
organization_name | string | No | Name for the new organization. Required when not using an invite. |
invite_token | string | No | Invitation token from an organization invite email |
Response:
{
"access_token": "eyJ...",
"token_type": "bearer",
"user_id": 1,
"email": "user@example.com",
"organization_id": 1,
"role": "admin",
"email_verified": false
}
Errors:
409— Email already registered.400— Invalid, expired, or already-redeemed invite, or the invite was sent to a different email address.422—organization_namemissing on a self-serve (non-invite) signup.
Login
Authenticate and receive a JWT token.
POST /api/v1/auth/login
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email address |
password | string | Yes | Account password |
Response: Same shape as the register response (including email_verified).
Errors:
401— Invalid credentials.
Change Password
Change the authenticated user’s password.
POST /api/v1/auth/change-password
Auth: Bearer token required.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
current_password | string | Yes | Your current password |
new_password | string | Yes | The new password |
Response:
{
"status": "ok"
}
To reset a forgotten password instead, use POST /api/v1/auth/forgot-password (body: email) to receive a reset link by email, then POST /api/v1/auth/reset-password (body: token, new_password).
Delete Account
Permanently delete the authenticated user’s account.
DELETE /api/v1/auth/account
Auth: Bearer token required.
Response:
{
"status": "account_deleted"
}
Verify Email
Confirm an email address using the token from the verification email sent at registration.
POST /api/v1/auth/verify-email
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Verification token from the emailed link |
Response:
{
"status": "verified"
}
Errors:
400— Invalid or expired verification link.
Resend Verification Email
Request a fresh verification email. Always returns 200 regardless of whether the address exists or is already verified.
POST /api/v1/auth/resend-verification
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email address |
Response:
{
"status": "ok"
}
Forgot Password
Request a password-reset email. Always returns 200 regardless of whether the address exists.
POST /api/v1/auth/forgot-password
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email address |
Response:
{
"status": "ok"
}
Reset Password
Set a new password using the token from a password-reset email. The token is single-use: it becomes invalid once the password has been changed. Completing a reset also marks the email address as verified.
POST /api/v1/auth/reset-password
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Reset token from the emailed link |
new_password | string | Yes | The new password (min 8 characters) |
Response:
{
"status": "ok"
}
Errors:
400— Invalid, expired, or already-used reset link.
Organizations
List Organizations
List all organizations the authenticated user belongs to.
GET /api/v1/organizations/
Auth: Bearer token required.
Response:
[
{
"id": 1,
"name": "example.com",
"created_at": "2025-01-15T10:30:00"
}
]
Get Organization
GET /api/v1/organizations/{org_id}
Auth: Bearer token required. Viewer role or above.
Response:
{
"id": 1,
"name": "example.com",
"created_at": "2025-01-15T10:30:00"
}
Update Organization
PATCH /api/v1/organizations/{org_id}
Auth: Bearer token required. Admin role.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | New organization name |
Response:
{
"id": 1,
"name": "New Name"
}
Team Members
List Members
GET /api/v1/organizations/{org_id}/members
Auth: Bearer token required. Viewer role or above.
Response:
[
{
"id": 1,
"user_id": 1,
"email": "admin@example.com",
"role": "admin",
"joined_at": "2025-01-15T10:30:00"
}
]
Invite Member
Creates an email-bound invitation. The invitee receives an email with a link to join; invites expire after 7 days, and re-inviting the same address replaces any outstanding invite.
POST /api/v1/organizations/{org_id}/invites
Auth: Bearer token required. Admin role.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email of the user to invite |
role | string | No | One of admin, developer, viewer. Default: developer |
Response:
{
"token": "xxxxxxxxxxxxxxxxxxxxxxxx",
"expires_at": "2026-07-10T12:00:00",
"email": "dev@example.com"
}
Errors:
400— User is already a member, or the role is invalid.
Remove Member
DELETE /api/v1/organizations/{org_id}/members/{member_id}
Auth: Bearer token required. Admin role.
Response:
{
"status": "removed"
}
API Tokens
API tokens provide programmatic access to the VibeView API. They work the same way as Bearer tokens obtained from login.
List API Tokens
GET /api/v1/organizations/{org_id}/api-tokens
Auth: Bearer token required. Developer role or above.
Response:
[
{
"id": 1,
"label": "CI Pipeline",
"token_prefix": "tok_abc12345****",
"created_by": "admin@example.com",
"role": "developer",
"used": false,
"created_at": "2025-01-15T10:30:00"
}
]
Create API Token
POST /api/v1/organizations/{org_id}/api-tokens
Auth: Bearer token required. Admin role.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Human-readable label for the token |
role | string | No | One of admin, developer, viewer. Default: developer |
Response:
{
"token": "tok_...",
"id": 1,
"label": "CI Pipeline"
}
The full token value is only returned once at creation time. Store it securely.
Delete API Token
DELETE /api/v1/organizations/{org_id}/api-tokens/{token_id}
Auth: Bearer token required. Admin role.
Response:
{
"status": "deleted"
}
Audit Events
List Audit Events
GET /api/v1/organizations/{org_id}/audit-events
Auth: Bearer token required. Viewer role or above.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
action | string | Filter by action (substring match, e.g. session.created) |
user_email | string | Filter by user email (exact match) |
resource_type | string | Filter by resource type (e.g. session, app, test_suite) |
time_range | string | One of last-hour, last-day, last-week, last-month |
limit | int | Max results (default 50, max 200) |
offset | int | Pagination offset (default 0) |
Response:
{
"total": 150,
"events": [
{
"id": 1,
"user_email": "admin@example.com",
"action": "session.created",
"resource_type": "session",
"resource_id": "abc-123",
"details": null,
"created_at": "2025-01-15T10:30:00"
}
]
}
Export Audit Events (CSV)
GET /api/v1/organizations/{org_id}/audit-events/csv
Auth: Bearer token required. Viewer role or above.
Returns a CSV file download with columns: Date, User, Action, Resource Type, Resource ID, Details.
Session Defaults
Get Session Defaults
GET /api/v1/organizations/{org_id}/session-defaults
Auth: Bearer token required. Viewer role or above.
Response:
{
"session_timeout": "30m",
"session_permissions": "full",
"allow_streaming_overage": false
}
Update Session Defaults
PATCH /api/v1/organizations/{org_id}/session-defaults
Auth: Bearer token required. Admin role.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
session_timeout | string | No | Default session timeout |
session_permissions | string | No | Default session permissions |
allow_streaming_overage | boolean | No | Keep streaming past the plan’s included minutes by drawing on usage credit (see Billing) |
Apps
Upload App
Upload a new application binary. Supported formats: .app (bare or .zip/.tar.gz/.tgz archive) for iOS, and .apk/.apks for Android. Maximum file size: 500 MB. The file content is validated against expected magic bytes.
POST /api/v1/apps/upload
Auth: Bearer token required. Developer role or above.
Request: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | App binary (.app/.zip/.tar.gz/.tgz for iOS, .apk/.apks for Android) |
name | string | No | App display name (auto-detected) |
package_name | string | No | Package identifier (auto-detected) |
platform | string | No | ios or android (auto-detected from extension) |
note | string | No | Optional note stored on the new build (hard cap 500 chars; 200 recommended) |
Response:
{
"id": 1,
"name": "My App",
"package_name": "com.example.myapp",
"platform": "android",
"icon_url": "/uploads/apps/icon.png",
"created_at": "2025-01-15T10:30:00"
}
List Apps
GET /api/v1/apps/
Auth: Bearer token required. Viewer role or above.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
platform | string | Filter by ios or android |
Response:
[
{
"id": 1,
"name": "My App",
"package_name": "com.example.myapp",
"platform": "android",
"icon_url": "/uploads/apps/icon.png",
"last_started_at": "2025-01-20T14:00:00",
"updated_at": "2025-01-20T14:00:00",
"created_at": "2025-01-15T10:30:00"
}
]
Get App
Returns app details including all builds.
GET /api/v1/apps/{app_id}
Auth: Bearer token required. Viewer role or above.
Response:
{
"id": 1,
"name": "My App",
"package_name": "com.example.myapp",
"platform": "android",
"icon_url": "/uploads/apps/icon.png",
"last_started_at": null,
"latest_version": "1.2.0",
"latest_build_number": "42",
"latest_uploaded_at": "2025-01-20T14:00:00",
"builds": [
{
"id": 1,
"version": "1.2.0",
"build_number": "42",
"file_size": 15728640,
"uploaded_by": "dev@example.com",
"uploaded_at": "2025-01-20T14:00:00",
"tags": [],
"min_sdk_version": "21",
"target_sdk_version": "34",
"permissions": ["android.permission.INTERNET"],
"metadata": {}
}
]
}
Delete App
DELETE /api/v1/apps/{app_id}
Auth: Bearer token required. Developer role or above.
Response:
{
"status": "deleted"
}
Upload Build
Upload a new build for an existing app.
POST /api/v1/apps/{app_id}/builds
Auth: Bearer token required. Developer role or above.
Request: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | App binary |
version | string | No | Version string (auto-detected) |
note | string | No | Optional note stored on the new build (hard cap 500 chars; 200 recommended) |
Response:
{
"id": 2,
"version": "1.3.0",
"build_number": "43",
"file_size": 16000000,
"uploaded_at": "2025-01-21T09:00:00",
"note": "Fix login crash — hotfix for ticket #1234"
}
Sessions
Sessions represent live simulator/emulator instances. The lifecycle is: create -> start -> use -> stop.
Create Session
POST /api/v1/sessions/
Auth: Bearer token required.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
device_type | string | Yes | ios or android |
device_category | string | No | phone, tablet, or tv. Omit for any handheld. |
specific_device_id | string | No | Request a specific device by its UUID |
Response:
{
"id": 1,
"session_id": "uuid-string",
"device_type": "ios",
"device_category": "phone",
"status": "pending",
"device_id": null,
"device_name": null,
"os_version": null,
"user_email": null,
"webrtc_room_id": null,
"stream_url": null,
"start_time": null,
"end_time": null,
"duration_minutes": null
}
Note:
webrtc_room_idis deprecated and alwaysnull. It will be removed in a future API version.
Errors:
404— Specific device not found.409— Specific device is not available.
Start Session
Boot the simulator and begin streaming. Must be called after creating a session.
POST /api/v1/sessions/{session_id}/start
Auth: Bearer token required.
Request body (optional):
| Field | Type | Required | Description |
|---|---|---|---|
device_id | string | No | Specific device UUID to use |
Response: Same shape as the create response, with status updated to starting and device fields populated.
If no matching device is free, the request returns 202 Accepted and the session is queued:
{
"status": "queued",
"queue_entry_id": 42,
"position": 2,
"session_id": "uuid-string"
}
The session starts automatically when a device frees up. Use the queue endpoints below to leave the queue.
Errors:
400— Session already started.404— Session not found.
Leave Device Queue
Remove a queued session start from the device queue. Two equivalent forms are provided; the POST variant exists for navigator.sendBeacon compatibility (leaving the queue on page unload).
DELETE /api/v1/sessions/queue/{queue_entry_id}
POST /api/v1/sessions/queue/{queue_entry_id}/leave
Auth: Bearer token required. Only the user who queued the entry can remove it.
queue_entry_id is the integer returned in the 202 queued response from Start Session.
Response:
{
"status": "ok"
}
Errors:
404— Queue entry not found or already processed.
Get Session
GET /api/v1/sessions/{session_id}
Auth: Bearer token required.
Response: Session object.
List Sessions
GET /api/v1/sessions/
Auth: Bearer token required.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status: pending, starting, active, stopped, failed |
device_type | string | Filter by ios or android |
user_id | int | Filter by user ID |
time_range | string | One of last-hour, last-day, last-week, last-month |
search | string | Search filter |
limit | int | Max results (default 50, max 200) |
offset | int | Pagination offset (default 0) |
Response: Array of session objects.
Stop Session
Stop the simulator and release the device.
DELETE /api/v1/sessions/{session_id}
Auth: Bearer token required.
Response:
{
"status": "ok"
}
Export Sessions (CSV)
GET /api/v1/sessions/export/csv
Auth: Bearer token required.
Returns a CSV file download with session history.
Control Session
Send input commands to the running simulator.
POST /api/v1/sessions/{session_id}/control
Auth: Bearer token required.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | One of: tap, swipe, rotate, button, key, screenshot, touch_start, touch_move, touch_end |
x | float | No | X coordinate in pixels of the streamed video (0 - 10000) |
y | float | No | Y coordinate in pixels of the streamed video (0 - 10000) |
x2 | float | No | End X coordinate in pixels (for swipe) |
y2 | float | No | End Y coordinate in pixels (for swipe) |
duration | int | No | Duration in milliseconds |
rotation | int | No | Rotation angle: 0, 90, 180, 270 |
button | string | No | iOS button: HOME, LOCK, SIRI |
keycode | string | No | Android keycode: BACK, HOME, MENU, DPAD_UP, etc. |
Response:
{
"status": "ok"
}
Install App (Upload)
Upload and install an app binary directly into the running session.
POST /api/v1/sessions/{session_id}/install-app
Auth: Bearer token required.
Request: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | App binary (.app/.zip/.tar.gz/.tgz for iOS, .apk/.apks for Android) |
launch_after_install | boolean | No | Launch app after install (default: false) |
Install Build
Install an app from an existing build record (no file upload needed).
POST /api/v1/sessions/{session_id}/install-build
Auth: Bearer token required.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
build_id | int | Yes | ID of the build to install |
launch_after_install | boolean | No | Launch app after install (default: true) |
grant_permissions | boolean | No | Auto-grant permissions (default: true) |
Launch App
Launch an already-installed app on the device.
POST /api/v1/sessions/{session_id}/launch-app
Auth: Bearer token required.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
package_name | string | Yes | App package identifier |
activity_name | string | No | Android activity to launch |
List Installed Apps
Request the list of installed apps on the device. The result is delivered asynchronously via WebSocket.
GET /api/v1/sessions/{session_id}/apps
Auth: Bearer token required.
Uninstall App
DELETE /api/v1/sessions/{session_id}/apps/{package_name}
Auth: Bearer token required.
Response:
{
"status": "ok",
"message": "Uninstalling com.example.myapp"
}
Devices
List Devices
List devices registered with the platform. Only devices currently available or busy are returned.
GET /api/v1/devices/
Auth: Bearer token required.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
device_type | string | Filter by ios or android |
category | string | Filter by phone, tablet, or tv |
Response:
[
{
"id": 1,
"device_id": "ABCD1234-...",
"worker_id": "worker-1",
"name": "iPhone 16",
"type": "ios",
"category": "phone",
"os_version": "18.0",
"status": "available"
}
]
device_id is the value to pass as specific_device_id when creating a session, or as device_id on the CI run endpoints.
List Device Pools
Grouped view of the device list: one entry per (model, OS version, platform, category) combination, with availability counts. This is what the session-start device picker uses.
GET /api/v1/devices/pools
Auth: Bearer token required.
Response:
[
{
"group_key": "ios|phone|iPhone 16|18.0",
"model": "iPhone 16",
"os_version": "18.0",
"device_type": "ios",
"category": "phone",
"total": 4,
"free": 3
}
]
Groups whose devices are all currently unreachable are omitted; they reappear when capacity returns.
Get Device
GET /api/v1/devices/{device_id}
Auth: Bearer token required.
device_id is the string device identifier (the device_id field from the list response, not the numeric id).
Response: A single device object (same shape as the list entries).
Errors:
404— Device not found.
Tests
ID format: The
{suite_id},{case_id},{run_id}, and{suite_run_id}path parameters are string public IDs (thepublic_idfield in responses, e.g."suite_abc123"), not the numericid. App references in query and body fields generally take the app’spublic_idas well — Create Suite additionally accepts a legacy numericapp_id. One exception on run IDs: the AI suite-run cancel endpoint takes the numeric ID.
Test Suites
List Suites
GET /api/v1/tests/suites
Auth: Bearer token required. Viewer role or above.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
app_id | string | Filter suites by app public ID (e.g. ?app_id=app_xyz789) |
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:
[
{
"id": 1,
"public_id": "suite_abc123",
"name": "Login Flow",
"description": "Tests for the login feature",
"app_id": 1,
"app_public_id": "app_xyz789",
"app_name": "My App",
"platform": "ios",
"case_count": 5,
"reset_strategy": "clear_data",
"context": null,
"variables": {},
"last_run_status": "passed",
"last_run_at": "2025-01-20T14:00:00",
"created_at": "2025-01-15T10:30:00",
"updated_at": "2025-01-20T14:00:00",
"tags": [{"name": "profiles", "display_name": "Profiles"}]
}
]
Create Suite
POST /api/v1/tests/suites
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Suite name |
description | string | No | Suite description |
app_public_id | string | No | Associated app public ID (preferred) |
app_id | int | No | Associated app numeric ID (legacy alternative to app_public_id) |
platform | string | No | ios or android |
tags | string[] | No | Optional list of tags. Each tag must match ^[a-z0-9_-]{1,50}$. Auto-creates new tags within the org (case-insensitive uniqueness; first-seen casing becomes display_name). |
Response includes tags: [{name, display_name}] reflecting the suite’s current tags.
Export Suite as YAML
Downloads the suite and all its cases as a YAML file. Includes step mappings, source recordings, and device dimensions for portable hybrid replay.
GET /api/v1/tests/suites/{suite_id}/export-yaml
Auth: Bearer token required. Viewer role or above.
Response: YAML file download (text/yaml).
Import Suite from YAML
Creates a test suite from an uploaded YAML file. Optionally links to an app.
POST /api/v1/tests/suites/import-yaml
Auth: Bearer token required. Developer role or above.
Body: multipart/form-data with a file field containing the YAML file.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
app_id | string | (Optional) App public ID to link the suite to |
If app_id is not provided but the YAML contains an appId field, the import attempts to match an existing app by package name.
Response:
{
"public_id": "my-suite-abc123",
"name": "My Test Suite",
"case_count": 5
}
Get Suite
Returns the suite with all its test cases.
GET /api/v1/tests/suites/{suite_id}
Auth: Bearer token required. Viewer role or above.
Update Suite
PATCH /api/v1/tests/suites/{suite_id}
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Suite name |
description | string | No | Suite description |
reset_strategy | string | No | One of relaunch, clear_data, reinstall |
context | string | No | Additional context passed to the AI agent |
variables | object | No | Key-value variables available during test execution |
visual_regression_warn_pct | int | No | Warn-tier threshold for visual regression (% changed pixels). null disables the warn tier. See Visual regression. |
visual_regression_fail_pct | int | No | Fail-tier threshold for visual regression (% changed pixels). null disables the fail tier. See Visual regression. |
tags | string[] | No | When present, replaces the suite’s tag set wholesale. An empty array clears all tags. Omit to leave existing tags unchanged. |
Delete Suite
DELETE /api/v1/tests/suites/{suite_id}
Auth: Bearer token required. Developer role or above.
List Org Tags
GET /api/v1/tests/tags
Auth: Bearer token required. Viewer role or above.
Lists every tag in the user’s organization with usage counts. Used by autocomplete UIs.
Response: 200 OK
[
{ "name": "smoke", "display_name": "smoke", "suite_count": 7 },
{ "name": "profiles", "display_name": "Profiles", "suite_count": 3 }
]
Sorted by suite_count DESC, name ASC. Tags with zero suites are included.
Test Cases
Create Test Case (in a suite)
POST /api/v1/tests/suites/{suite_id}/cases
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Test case name |
steps | array | Yes | Array of step objects or strings |
assertions | array | No | Verification assertions |
mode | string | No | ai or recorded (default: recorded) |
source_recording | array | No | Raw recording actions for hybrid replay |
source_device_width | int | No | Device width used during recording |
source_device_height | int | No | Device height used during recording |
Response:
{
"id": 1,
"suite_id": 1,
"name": "Login with valid credentials",
"mode": "ai",
"steps": [...],
"assertions": [...],
"step_count": 5,
"has_source_recording": true,
"source_recording_summary": {
"total_actions": 12,
"gesture_counts": {"tap": 3, "swipe": 1}
},
"source_gesture_list": [...],
"step_gesture_groups": [...],
"source_device_width": 390,
"source_device_height": 844,
"context": null,
"created_at": "2025-01-15T10:30:00",
"updated_at": "2025-01-15T10:30:00"
}
List Test Cases (in a suite)
A suite’s cases are returned embedded in the suite object — fetch them with Get Suite (GET /api/v1/tests/suites/{suite_id}). There is no separate list endpoint.
Get Test Case
Returns the test case. Works for both suite-attached and standalone test cases. To fetch a case’s run history, use GET /api/v1/tests/cases/{case_id}/runs.
GET /api/v1/tests/cases/{case_id}
Auth: Bearer token required. Viewer role or above.
Update Test Case
PATCH /api/v1/tests/cases/{case_id}
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Test case name |
steps | array | No | Updated steps |
assertions | array | No | Updated assertions |
context | string | No | Additional context for the AI |
app_id | string | No | Associated app public ID. Only settable on standalone cases — returns 400 if the case is attached to a suite (the suite owns the app linkage). |
sub_flow_ids | array | No | Ordered list of reusable case IDs to run before this case’s own steps |
source_recording | array | No | Raw recording actions for hybrid replay |
fuzzy_match | boolean | No | Enable fuzzy coordinate matching during replay |
is_reusable | boolean | No | Mark case as a reusable sub-flow. Setting true requires no parent suite — returns 400 if the case is attached to a suite. |
Delete Test Case
DELETE /api/v1/tests/cases/{case_id}
Auth: Bearer token required. Developer role or above.
Copy Test Case
Copy a test case to another suite.
POST /api/v1/tests/suites/{suite_id}/cases/{case_id}/copy
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
target_suite_id | string | Yes | Destination suite public ID |
Create Standalone Test Case
Create a test case that is not attached to any suite. If a standalone case with the same name already exists for the app, it is updated in place instead of duplicated.
POST /api/v1/tests/cases
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Test case name |
app_id | string | Yes | App public ID the case belongs to |
steps | array | Yes | Array of step objects or strings |
assertions | array | No | Verification assertions |
mode | string | No | ai or recorded (default: recorded) |
context | string | No | Additional context for the AI |
source_recording | array | No | Raw recording actions for hybrid replay |
source_device_width | int | No | Device width used during recording |
source_device_height | int | No | Device height used during recording |
source_platform | string | No | ios, android, tvos, or androidtv (defaults to the app’s platform) |
fuzzy_match | boolean | No | Enable fuzzy coordinate matching during replay (default: true) |
Response: The created (or updated) test case object, same shape as Create Test Case (in a suite).
Errors:
404— App not found in your organization.
List Standalone Test Cases
List test cases that are not attached to any suite.
GET /api/v1/tests/cases
Auth: Bearer token required. Viewer role or above.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
reusable | boolean | true — only reusable sub-flows; false — only non-reusable; omit for all |
Response: Array of test case objects (most recent first), each including recent_run_statuses for the last 10 runs.
Test Case Runs
List Runs for a Test Case
GET /api/v1/tests/cases/{case_id}/runs
Auth: Bearer token required. Viewer role or above.
Returns all runs for the specified test case, ordered by most recent first.
Create a Run Record
POST /api/v1/tests/cases/{case_id}/run
Auth: Bearer token required. Viewer role or above.
Creates a run record for the case in pending status — it does not execute the test. To actually execute a test, use Run AI Test (POST /api/v1/ai/test/run) or the CI endpoints.
Response:
{
"id": 1,
"public_id": "tr7abc123x",
"test_case_id": 1,
"status": "pending",
"started_at": "2025-01-20T14:00:00",
"finished_at": null,
"result": null,
"reasoning_log": null,
"error_message": null,
"duration_ms": null,
"model_used": null,
"tokens_used": null,
"cost_micro": null
}
List Baselines for a Test Case
GET /api/v1/tests/cases/{case_id}/baselines
Auth: Bearer token required. Viewer role or above.
Returns all baseline image records for the test case.
Get Baseline Image
GET /api/v1/tests/cases/{case_id}/baselines/step_{step_index}.jpg
Auth: Bearer token required. Viewer role or above.
Returns the baseline screenshot for the specified step as a JPEG image.
Test Runs (run-level operations)
Update Test Run
PATCH /api/v1/tests/runs/{run_id}
Auth: Bearer token required.
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | Yes | One of pending, running, passed, failed, error |
result_json | string | No | JSON-encoded result data |
error_message | string | No | Error message if failed |
duration_ms | int | No | Execution duration in milliseconds |
Get Test Run
GET /api/v1/tests/runs/{run_id}
Auth: Bearer token required.
Cancel Test Run
Cancel a running AI test execution.
POST /api/v1/tests/runs/{run_id}/cancel
Auth: Bearer token required.
Response:
{
"status": "cancellation_requested"
}
Errors:
400— Run is not active or already finished.
Suite Runs
Get Suite Run
Get details of a suite run including all linked test runs.
GET /api/v1/tests/suite-runs/{suite_run_id}
Auth: Bearer token required.
Response:
{
"id": 1,
"suite_id": 1,
"session_id": "uuid-string",
"status": "passed",
"reset_strategy": "clear_data",
"started_at": "2025-01-20T14:00:00",
"finished_at": "2025-01-20T14:05:00",
"duration_ms": 300000,
"error_message": null,
"test_runs": [
{
"id": 1,
"test_case_id": 1,
"status": "passed",
"duration_ms": 15000,
"error_message": null
}
]
}
AI
Get AI Config
Get the organization’s AI configuration (default provider and model).
GET /api/v1/ai/config
Auth: Bearer token required.
Response:
{
"default_provider": "vibeview",
"default_model": "claude-4-sonnet"
}
Update AI Config
PUT /api/v1/ai/config
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
default_provider | string | No | Provider: vibeview, anthropic, openai, google, openrouter |
default_model | string | No | Model identifier (e.g. claude-4-sonnet). Append :byok for own-key models |
List Models
List all available AI models, including built-in VibeView models and any models available via BYOK (Bring Your Own Key) API keys.
GET /api/v1/ai/models
Auth: Bearer token required.
Response:
[
{
"id": "claude-4-sonnet",
"name": "claude-4-sonnet (VibeView)",
"provider": "vibeview",
"builtin": true
},
{
"id": "gpt-4o:byok",
"name": "gpt-4o (own key)",
"provider": "openai",
"builtin": false
}
]
List OpenRouter Models
List vision-capable models available through OpenRouter. With an OpenRouter BYOK key configured, the list is fetched live from the OpenRouter API; without one, a curated recommended set is returned. All entries are own-key (:byok) models — runs are billed by OpenRouter directly, not by VibeView.
GET /api/v1/ai/models/openrouter
Auth: Bearer token required.
Response: Array of model objects, same shape as List Models (builtin is always false).
Add API Key (BYOK)
Store an API key for a provider to use your own models. Keys are encrypted at rest. If a key already exists for the provider, it is replaced.
POST /api/v1/ai/keys
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | anthropic, openai, google, or openrouter |
api_key | string | Yes | The API key |
Response:
{
"id": 1,
"provider": "openai",
"key_hint": "...abcd",
"created_at": "2025-01-15T10:30:00"
}
List API Keys
GET /api/v1/ai/keys
Auth: Bearer token required.
Response: Array of key objects (without the actual key values).
Delete API Key
DELETE /api/v1/ai/keys/{key_id}
Auth: Bearer token required. Developer role or above.
Get Usage
Get AI usage summary for the organization, broken down by total, BYOK, VibeView-hosted, and per-model.
GET /api/v1/ai/usage
Auth: Bearer token required.
Response:
{
"total": {
"input_tokens": 150000,
"output_tokens": 30000,
"cost_micro": 2500000,
"runs_count": 45
},
"byok": {
"input_tokens": 50000,
"output_tokens": 10000,
"cost_micro": 480000,
"runs_count": 15
},
"vibeview": {
"input_tokens": 100000,
"output_tokens": 20000,
"cost_micro": 2500000,
"runs_count": 30
},
"by_model": [
{
"model": "claude-4-sonnet",
"runs": 30,
"cost_micro": 2500000
}
]
}
Cost is reported in micro-credits (1,000,000 = $1.00). For the vibeview bucket it is the actual billed amount deducted from your usage credit. For the byok bucket it is an estimate computed from public list pricing — VibeView deducts nothing for BYOK runs, and your provider bills you directly, so the exact amount may differ. byok covers runs made with your own API key; vibeview covers VibeView-provided runs. The two are reported separately; the total bucket combines them, so treat its cost as “actual + estimate” rather than pure billed spend.
Generate Test from Recording
Analyze a recorded user session and generate natural-language test steps using AI.
POST /api/v1/ai/generate-test
Auth: Bearer token required.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
recording | array | Yes | Array of recorded action objects |
app_name | string | No | Name of the app being tested |
context | string | No | Additional context for the AI |
model | string | No | Model to use (defaults to org config) |
Response:
{
"name": "Login with valid credentials",
"steps": [
{
"text": "Tap on the email field and type 'user@example.com'",
"source_action_index": 0,
"screenshot": "data:image/png;base64,..."
}
],
"assertions": [
"Verify the dashboard screen is displayed"
]
}
Run AI Test
Launch an AI-powered test execution against a live device session. The test runs asynchronously; poll the test run endpoint for results.
POST /api/v1/ai/test/run
Auth: Bearer token required. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
test_case_public_id | string | No* | Public ID of the test case to execute |
test_case_id | int | No* | Numeric ID of the test case (legacy alternative) |
session_id | string | Yes | Active session to run the test against |
model | string | No | Model to use (defaults to org config) |
provider | string | No | Provider override |
mode | string | No | hybrid (default), ai (pure AI), or replay (replay only) |
fuzzy_match | boolean | No | Per-run override of the case’s fuzzy-match default |
build_id | int | No | Specific AppBuild id to install before the run. Defaults to the test case app’s latest build. Returns 400 if the case has no app attached, 404 if the build doesn’t exist, 403 if it belongs to a different app or organization. |
*Provide either test_case_public_id or test_case_id.
Response:
{
"run_id": 1,
"run_public_id": "tr7abc123x",
"status": "started",
"message": "Hybrid test started with 5 steps",
"warnings": null
}
warnings is null or a list of human-readable strings (e.g. usage-threshold warnings) suitable for surfacing to the user.
Execution modes:
- hybrid (default) — Replays recorded actions first, falls back to AI for any steps that fail during replay.
- ai — Pure AI execution; the agent interprets each step and interacts with the device.
- replay — Replay recorded actions only; no AI fallback.
Run Suite
Execute all test cases in a suite sequentially against a live session.
POST /api/v1/ai/suite/run
Auth: Bearer token required.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
suite_public_id | string | No* | Public ID of the suite to execute |
suite_id | int | No* | Numeric suite ID (legacy alternative) |
session_id | string | Yes | Active session to run against |
model | string | No | Model to use |
provider | string | No | Provider override |
mode | string | No | hybrid, ai, or replay |
fuzzy_match | boolean | No | Per-run override of each case’s fuzzy-match default |
build_id | int | No | Specific AppBuild id to install before the first case. Defaults to the suite app’s latest build. Returns 400 if no app is attached to the suite, 404 if the build does not exist, 403 if the build belongs to a different app or organization. |
*Provide either suite_public_id or suite_id.
Response:
{
"suite_run_id": 1,
"suite_run_public_id": "sr9def456y",
"status": "pending"
}
Cancel Suite Run
Stop an in-flight suite run. The currently executing case is cancelled and no further cases are started.
POST /api/v1/ai/suite/run/{suite_run_id}/cancel
Auth: Bearer token required.
suite_run_id is the numeric suite_run_id returned by Run Suite.
Response:
{
"status": "cancellation_requested"
}
Errors:
400— Suite run is not active or already finished.
Credits & Usage
Prepaid AI credit and usage reporting. Amounts are in micro-credits (1,000,000 = $1.00). See Usage Credits and Billing for how balances work.
Create Credit Top-Up
Start a Stripe Checkout to buy prepaid AI credit. Pass exactly one of bundle_key (a preset bundle from the balance response) or amount_usd (a custom whole-dollar amount within the server-advertised bounds). The credit is applied to your balance when the checkout completes.
POST /api/v1/credits/topup
Auth: Bearer token required. Admin role.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
bundle_key | string | No* | Preset bundle key (see bundles in the balance response) |
amount_usd | int | No* | Custom amount in whole USD (within custom_min_micro/custom_max_micro bounds) |
*Provide exactly one of the two.
Response:
{
"url": "https://checkout.stripe.com/..."
}
Redirect the user to url to complete payment.
Errors:
400— Neither or both fields provided, non-integer amount, or amount out of bounds.503— Billing is not available.
Get Credit Balance
GET /api/v1/credits/balance
Auth: Bearer token required.
Response:
{
"allowance_micro": 750000,
"prepaid_micro": 5000000,
"plan_allowance_micro": 1000000,
"plan_period_end": "2026-08-01T00:00:00",
"low_balance": false,
"bundles": [
{ "bundle_key": "bundle_10", "amount_micro": 10000000 }
],
"custom_min_micro": 5000000,
"custom_max_micro": 500000000
}
allowance_micro— remaining monthly plan allowance.prepaid_micro— remaining purchased credit.plan_period_end— when the allowance refills (nullon the Free plan).low_balance— server-computed flag for showing a low-balance warning.bundles,custom_min_micro,custom_max_micro— valid top-up options for Create Credit Top-Up.
Get Credit Ledger
Paginated history of credit movements: usage deductions, top-ups, and allowance refills (most recent first).
GET /api/v1/credits/ledger
Auth: Bearer token required.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
limit | int | Max entries (default 20, max 100) |
offset | int | Pagination offset (default 0) |
Response:
{
"total": 132,
"entries": [
{
"id": 990,
"created_at": "2026-07-01T12:00:00",
"entry_type": "usage_deduction",
"balance_bucket": "allowance",
"amount_micro": -42000,
"model": "claude-4-sonnet",
"test_run_id": 512,
"is_streaming": false
}
]
}
entry_type is one of usage_deduction, prepaid_topup, subscription_refill, and similar; balance_bucket is allowance or prepaid. amount_micro is signed (deductions are negative). model is set on AI usage deductions; is_streaming marks streaming-overage deductions.
Get Usage Dashboard
Combined current-month usage metrics with plan limits — the data behind the Settings → Usage page.
GET /api/v1/usage/dashboard
Auth: Bearer token required.
Response:
{
"plan_id": "pro",
"plan_name": "Pro",
"ai_credit_vibeview": { "current": 0.25, "limit": 10.0, "unit": "USD" },
"ai_tokens_byok": { "current": 60000, "limit": null, "unit": "tokens" },
"streaming_minutes": { "current": 340, "limit": 1000, "unit": "minutes" },
"active_sessions": { "current": 1, "limit": 2, "unit": "sessions" },
"monthly_sessions": 87,
"ai_cost_micro": 250000,
"byok_cost_micro": 480000,
"ai_runs": 30,
"byok_runs": 15,
"by_model": [
{ "model": "claude-4-sonnet", "runs": 30, "cost_micro": 250000 }
],
"streaming_overage_minutes": 0,
"streaming_overage_micro": 0
}
Each metric object has current, limit (null = unlimited), and unit. Streaming minutes include live in-progress sessions, so the number matches what the streaming meter enforces.
CI
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.
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 (default), tablet, or tv |
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 suite app’s latest build. Returns 400 if no app is attached to the suite, 404 if the build does not exist, 403 if the build belongs to a different app or organization. |
Response:
{"run_id": "suite-run-public-id", "status": "pending"}
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 (default), tablet, or tv |
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 test case app’s latest build. Returns 400 if no app is attached to the test case, 404 if the build does not exist, 403 if the build belongs to a different app or organization. |
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.
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.
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 | Execution mode (hybrid, ai, replay). |
device_category | string | No | phone (default), tablet, or tv. |
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. |
Response: 200 OK
{
"aggregate_id": "agg_abc123",
"matched_suite_ids": ["suite_a", "suite_b"],
"status_url": "/api/v1/ci/multi-suite-runs/agg_abc123"
}
Errors:
404— no suites matched the tag filter400—build_idis not valid for one or more matched suites (response body lists the offending suite IDs)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 error > cancelled > failed > 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.
Errors:
404— multi-suite run not found in user’s org
Error Responses
All error responses follow a consistent format:
{
"detail": "Human-readable error message"
}
Common HTTP status codes:
| Code | Meaning |
|---|---|
| 400 | Bad request (invalid input, missing fields) |
| 401 | Unauthorized (missing or invalid token) |
| 403 | Forbidden (insufficient role) |
| 404 | Resource not found |
| 409 | Conflict (duplicate resource) |
| 502 | AI model error (upstream provider failure) |
Rate Limits
API token usage is tracked per organization. Requests are rate-limited per client:
- Most endpoints: 100 requests/minute.
- App uploads and AI test runs (
POST /api/v1/ai/test/run): 10 requests/minute. - Auth endpoints (register, login, change-password): 5 requests/minute.
Exceeding a limit returns 429 Too Many Requests. AI test execution is additionally subject to upstream LLM provider rate limits.
Webhook Events
VibeView emits webhook events for tests, visual regressions, warnings, platform actions, and billing. Subscribe an endpoint to specific events under Settings → Integrations, or via the API:
GET /api/v1/integrations/events/catalog— list every event type the system emits.GET/PUT /api/v1/integrations/webhooks/{id}/subscriptions— read or replace the event-type set for a webhook.POST /api/v1/integrations/webhooks/{id}/regenerate-secret— rotate the HMAC signing secret. The plaintext is returned once.GET /api/v1/integrations/webhooks/{id}/deliveries— last 30 days of delivery attempts, with payload and response excerpts.POST /api/v1/integrations/webhooks/{id}/deliveries/{delivery_id}/redeliver— redeliver a specific past event.
See Webhooks and Slack for the full event catalog, payload schemas for every event, and signing-verification snippets in Node.js, Python, and Go.