V
Vibeview
Pricing

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:

FieldTypeRequiredDescription
emailstringYesUser email address
passwordstringYesAccount password (min 8 characters)
organization_namestringNoName for the new organization. Required when not using an invite.
invite_tokenstringNoInvitation 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.
  • 422organization_name missing on a self-serve (non-invite) signup.

Login

Authenticate and receive a JWT token.

POST /api/v1/auth/login

Request body:

FieldTypeRequiredDescription
emailstringYesUser email address
passwordstringYesAccount 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:

FieldTypeRequiredDescription
current_passwordstringYesYour current password
new_passwordstringYesThe 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:

FieldTypeRequiredDescription
tokenstringYesVerification 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:

FieldTypeRequiredDescription
emailstringYesUser 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:

FieldTypeRequiredDescription
emailstringYesUser 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:

FieldTypeRequiredDescription
tokenstringYesReset token from the emailed link
new_passwordstringYesThe 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:

FieldTypeRequiredDescription
namestringNoNew 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:

FieldTypeRequiredDescription
emailstringYesEmail of the user to invite
rolestringNoOne 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:

FieldTypeRequiredDescription
labelstringYesHuman-readable label for the token
rolestringNoOne 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:

ParameterTypeDescription
actionstringFilter by action (substring match, e.g. session.created)
user_emailstringFilter by user email (exact match)
resource_typestringFilter by resource type (e.g. session, app, test_suite)
time_rangestringOne of last-hour, last-day, last-week, last-month
limitintMax results (default 50, max 200)
offsetintPagination 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:

FieldTypeRequiredDescription
session_timeoutstringNoDefault session timeout
session_permissionsstringNoDefault session permissions
allow_streaming_overagebooleanNoKeep 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

FieldTypeRequiredDescription
filefileYesApp binary (.app/.zip/.tar.gz/.tgz for iOS, .apk/.apks for Android)
namestringNoApp display name (auto-detected)
package_namestringNoPackage identifier (auto-detected)
platformstringNoios or android (auto-detected from extension)
notestringNoOptional 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:

ParameterTypeDescription
platformstringFilter 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

FieldTypeRequiredDescription
filefileYesApp binary
versionstringNoVersion string (auto-detected)
notestringNoOptional 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:

FieldTypeRequiredDescription
device_typestringYesios or android
device_categorystringNophone, tablet, or tv. Omit for any handheld.
specific_device_idstringNoRequest 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_id is deprecated and always null. 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):

FieldTypeRequiredDescription
device_idstringNoSpecific 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:

ParameterTypeDescription
statusstringFilter by status: pending, starting, active, stopped, failed
device_typestringFilter by ios or android
user_idintFilter by user ID
time_rangestringOne of last-hour, last-day, last-week, last-month
searchstringSearch filter
limitintMax results (default 50, max 200)
offsetintPagination 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:

FieldTypeRequiredDescription
actionstringYesOne of: tap, swipe, rotate, button, key, screenshot, touch_start, touch_move, touch_end
xfloatNoX coordinate in pixels of the streamed video (0 - 10000)
yfloatNoY coordinate in pixels of the streamed video (0 - 10000)
x2floatNoEnd X coordinate in pixels (for swipe)
y2floatNoEnd Y coordinate in pixels (for swipe)
durationintNoDuration in milliseconds
rotationintNoRotation angle: 0, 90, 180, 270
buttonstringNoiOS button: HOME, LOCK, SIRI
keycodestringNoAndroid 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

FieldTypeRequiredDescription
filefileYesApp binary (.app/.zip/.tar.gz/.tgz for iOS, .apk/.apks for Android)
launch_after_installbooleanNoLaunch 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:

FieldTypeRequiredDescription
build_idintYesID of the build to install
launch_after_installbooleanNoLaunch app after install (default: true)
grant_permissionsbooleanNoAuto-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:

FieldTypeRequiredDescription
package_namestringYesApp package identifier
activity_namestringNoAndroid 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:

ParameterTypeDescription
device_typestringFilter by ios or android
categorystringFilter 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 (the public_id field in responses, e.g. "suite_abc123"), not the numeric id. App references in query and body fields generally take the app’s public_id as well — Create Suite additionally accepts a legacy numeric app_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:

ParameterTypeDescription
app_idstringFilter suites by app public ID (e.g. ?app_id=app_xyz789)
tagsstringComma-separated tag names to filter by (e.g. ?tags=smoke,profiles)
matchstringany (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:

FieldTypeRequiredDescription
namestringYesSuite name
descriptionstringNoSuite description
app_public_idstringNoAssociated app public ID (preferred)
app_idintNoAssociated app numeric ID (legacy alternative to app_public_id)
platformstringNoios or android
tagsstring[]NoOptional 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:

ParameterTypeDescription
app_idstring(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:

FieldTypeRequiredDescription
namestringNoSuite name
descriptionstringNoSuite description
reset_strategystringNoOne of relaunch, clear_data, reinstall
contextstringNoAdditional context passed to the AI agent
variablesobjectNoKey-value variables available during test execution
visual_regression_warn_pctintNoWarn-tier threshold for visual regression (% changed pixels). null disables the warn tier. See Visual regression.
visual_regression_fail_pctintNoFail-tier threshold for visual regression (% changed pixels). null disables the fail tier. See Visual regression.
tagsstring[]NoWhen 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:

FieldTypeRequiredDescription
namestringYesTest case name
stepsarrayYesArray of step objects or strings
assertionsarrayNoVerification assertions
modestringNoai or recorded (default: recorded)
source_recordingarrayNoRaw recording actions for hybrid replay
source_device_widthintNoDevice width used during recording
source_device_heightintNoDevice 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:

FieldTypeRequiredDescription
namestringNoTest case name
stepsarrayNoUpdated steps
assertionsarrayNoUpdated assertions
contextstringNoAdditional context for the AI
app_idstringNoAssociated 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_idsarrayNoOrdered list of reusable case IDs to run before this case’s own steps
source_recordingarrayNoRaw recording actions for hybrid replay
fuzzy_matchbooleanNoEnable fuzzy coordinate matching during replay
is_reusablebooleanNoMark 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:

FieldTypeRequiredDescription
target_suite_idstringYesDestination 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:

FieldTypeRequiredDescription
namestringYesTest case name
app_idstringYesApp public ID the case belongs to
stepsarrayYesArray of step objects or strings
assertionsarrayNoVerification assertions
modestringNoai or recorded (default: recorded)
contextstringNoAdditional context for the AI
source_recordingarrayNoRaw recording actions for hybrid replay
source_device_widthintNoDevice width used during recording
source_device_heightintNoDevice height used during recording
source_platformstringNoios, android, tvos, or androidtv (defaults to the app’s platform)
fuzzy_matchbooleanNoEnable 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:

ParameterTypeDescription
reusablebooleantrue — 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:

ParameterTypeRequiredDescription
statusstringYesOne of pending, running, passed, failed, error
result_jsonstringNoJSON-encoded result data
error_messagestringNoError message if failed
duration_msintNoExecution 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:

FieldTypeRequiredDescription
default_providerstringNoProvider: vibeview, anthropic, openai, google, openrouter
default_modelstringNoModel 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:

FieldTypeRequiredDescription
providerstringYesanthropic, openai, google, or openrouter
api_keystringYesThe 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:

FieldTypeRequiredDescription
recordingarrayYesArray of recorded action objects
app_namestringNoName of the app being tested
contextstringNoAdditional context for the AI
modelstringNoModel 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:

FieldTypeRequiredDescription
test_case_public_idstringNo*Public ID of the test case to execute
test_case_idintNo*Numeric ID of the test case (legacy alternative)
session_idstringYesActive session to run the test against
modelstringNoModel to use (defaults to org config)
providerstringNoProvider override
modestringNohybrid (default), ai (pure AI), or replay (replay only)
fuzzy_matchbooleanNoPer-run override of the case’s fuzzy-match default
build_idintNoSpecific 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:

FieldTypeRequiredDescription
suite_public_idstringNo*Public ID of the suite to execute
suite_idintNo*Numeric suite ID (legacy alternative)
session_idstringYesActive session to run against
modelstringNoModel to use
providerstringNoProvider override
modestringNohybrid, ai, or replay
fuzzy_matchbooleanNoPer-run override of each case’s fuzzy-match default
build_idintNoSpecific 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:

FieldTypeRequiredDescription
bundle_keystringNo*Preset bundle key (see bundles in the balance response)
amount_usdintNo*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 (null on 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:

ParameterTypeDescription
limitintMax entries (default 20, max 100)
offsetintPagination 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:

FieldTypeRequiredDescription
suite_idstringYespublic_id of the suite
commit_shastringNoGit commit SHA for GitHub status checks
modelstringNoModel override
providerstringNoProvider override
modestringNohybrid, ai, or replay
device_categorystringNophone (default), tablet, or tv
device_idstringNoPin the run to a specific device (from List Devices). Mutually exclusive with device_category.
metadataobjectNoArbitrary JSON object passed through unchanged to webhook payloads for this run (e.g. PR number, branch).
build_idintNoSpecific 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:

FieldTypeRequiredDescription
test_idstringYespublic_id of the test case
commit_shastringNoGit commit SHA for GitHub status checks
modelstringNoModel override
providerstringNoProvider override
modestringNohybrid, ai, or replay
device_categorystringNophone (default), tablet, or tv
device_idstringNoPin the run to a specific device (from List Devices). Mutually exclusive with device_category.
metadataobjectNoArbitrary JSON object passed through unchanged to webhook payloads for this run (e.g. PR number, branch).
build_idintNoSpecific 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:

ParameterTypeDescription
tagsstringComma-separated tag names to filter by (e.g. ?tags=smoke,profiles)
matchstringany (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:

FieldTypeRequiredDescription
tagsstring[]YesTags to match (1–20). Each must satisfy ^[a-z0-9_-]{1,50}$.
matchstringNoany (default) or all.
commit_shastringNoPropagated to every child SuiteRun.
modelstringNoAI model.
providerstringNoAI provider.
modestringNoExecution mode (hybrid, ai, replay).
device_categorystringNophone (default), tablet, or tv.
device_idstringNoPin every run to a specific device (from List Devices). Mutually exclusive with device_category.
metadataobjectNoArbitrary JSON object passed through unchanged to webhook payloads for these runs (e.g. PR number, branch).
build_idintNoAppBuild 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 filter
  • 400build_id is 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:

CodeMeaning
400Bad request (invalid input, missing fields)
401Unauthorized (missing or invalid token)
403Forbidden (insufficient role)
404Resource not found
409Conflict (duplicate resource)
502AI 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.