These endpoints list available AI models, manage your provider keys, launch AI-driven test runs, and report per-run usage. Authentication, error shapes and rate limits are covered in the REST API overview.
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",
"max_iterations": 20,
"default_max_iterations": 20,
"max_iterations_overridden": false,
"max_run_spend_micro": 5000000,
"default_max_run_spend_micro": 5000000,
"max_run_spend_overridden": false,
"min_max_run_spend_micro": 500000,
"max_max_run_spend_micro": 100000000
}
Amounts ending in _micro are in micro-dollars (1,000,000 = $1.00). max_iterations is the AI agent’s turns per step and max_run_spend_micro the AI spending limit per run, each your organization’s setting or, when you have not set one, the default.
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 |
max_iterations | integer or null | No | AI agent turns per step, 1–30. null returns to the default |
max_run_spend_micro | integer or null | No | AI spending limit per run in micro-dollars, 500000–100000000 ($0.50–$100.00). null returns to the default ($5.00) |
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. The list is fetched live from the OpenRouter API with your OpenRouter BYOK key; without one, the list is empty. 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.
Draft Test Steps
Draft a test’s steps from a plain-language goal (the Describe tab of Create test).
POST /api/v1/ai/draft-steps
Auth: Bearer token required. Developer role or above. Limited to 10 requests a minute.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
app_id | string | Yes | App public ID; the app’s notes and the names of its test data are given to the model |
goal | string | Yes | What the test should check, 3-2000 characters |
group_ids | array | No | Suite public IDs (of this app) whose notes are given to the model too |
model | string | No | Model to use (defaults to org config) |
Response:
{
"name": "Save a destination",
"steps": [
{ "text": "Search for Kyoto and open the first result" },
{ "text": "Tap Save" },
{ "text": "Verify 'Kyoto' is listed under Saved" }
],
"model": "claude-4-sonnet",
"who_pays": "usage_credit",
"free_max_steps": null
}
who_pays is usage_credit, own_key or free (the Free plan’s free test paid for it). free_max_steps is the free test’s step limit (setups included) when the free test paid, otherwise null. Nothing is saved: create the test with POST /api/v1/tests/cases.
Errors:
403— drafting is refused;detailis{ "message": "...", "remedy": "add_key" | "buy_credits" | "upgrade" | null }.404— App or suite not found in your organization.400— a suite of another app.402— usage credit ran out during the draft.429— too many requests.502— the AI model failed, or returned no usable steps (an unusable answer is still billed).
Who Pays for a Draft
GET /api/v1/ai/draft-steps/payer?app_id={app_id}
Auth: Bearer token required. Developer role or above.
Returns { "who_pays", "model", "model_name", "message", "remedy", "free_max_steps" }. who_pays is usage_credit, own_key, free or blocked; when blocked, message and remedy say why and how to fix it. free_max_steps is the free test’s step limit when the free test pays.
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. Developer role or above.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
recording | array | Yes | Array of recorded action objects |
app_name | string | No | Name of the app being tested |
app_id | string | No | App public ID; the app’s notes are given to the model |
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,..."
},
{
"text": "Verify 'Dashboard' is visible",
"source_action_index": null,
"screenshot": null
}
],
"assertions": []
}
Checks are written as inline Verify ... steps where they belong. A Verify step has no recorded actions, so its source_action_index is null. assertions is deprecated and always [].
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 and POST /api/v1/ai/suite/run keep working. For new integrations, use POST /api/v1/sessions/{session_id}/runs to run on a session you hold, or POST /api/v1/tests/runs to run on devices of their own.
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 (Smart, default), ai (AI only), or replay (No AI) |
fuzzy_match | boolean | No | Deprecated. Accepted and ignored. Replay matches elements by id or exact text only (see Element Matching) |
build_id | int | No | The AppBuild id to record the run against. The run does not install anything: it tests whatever is already on the session’s device, so start the session with the build you want to test. When omitted, the run is recorded against the build the session installed. 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. |
group_public_id | string | No | The suite the run is started from. Its start state, notes (context) and screenshot thresholds apply; test data comes from the app. Omitted: the test’s suite when it is in exactly one; otherwise none. 404 for an unknown suite, 400 when the test is not in it. A test’s own start state (start_state) wins over the group’s; with no group and no setting a run starts from Clean app data. |
*Provide either test_case_public_id or test_case_id.
Response:
{
"run_id": 1,
"run_public_id": "tr7abc123x",
"status": "started",
"message": "AI 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.
AI use (mode):
- hybrid (Smart, default) — Replays recorded actions first, falls back to AI for any steps that fail during replay.
- ai (AI only) — the AI agent runs every step; the agent interprets each step and interacts with the device.
- replay (No AI) — Replay recorded actions only; no AI fallback. A test case with no recording is refused with
400rather than run as AI.
Run Suite
Execute all test cases in a suite sequentially against a live session.
POST /api/v1/ai/suite/run
Auth: Bearer token required. Developer role or above.
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; honored together with model |
mode | string | No | hybrid, ai, or replay |
fuzzy_match | boolean | No | Deprecated. Accepted and ignored (see Element Matching) |
build_id | int | No | The AppBuild id to record each case’s run against. The suite tests what is already on the session’s device, so start the session with the build you want to test. The exception is a suite whose reset strategy is reinstall, which reinstalls this build before each case; on Roku, clear_data also deletes the channel and installs this build again before each case. On Roku, a suite started while the session’s channel is still installing waits for the install, and errors with the install’s reason if it fails. When omitted, runs are recorded against the build the session installed. Returns 400 if no app is attached to the suite, 404 if your organization has no such build, 403 if the build belongs to a different app. Both errors list the app’s newest builds. |
*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. Developer role or above.
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.