{"openapi":"3.1.0","info":{"title":"QualGent API Service","description":"\n## QualGent Test Automation API\n\nThe QualGent API allows you to automate mobile application testing at scale.\n\n### Features\n\n* **App Upload**: Upload mobile application files (APK/IPA) with support for both single and chunked uploads\n* **Test Cases**: Manage and execute automated test cases for your applications\n* **Test Runs**: Queue, monitor, and track test execution across multiple devices\n* **Organization-Based**: All data is scoped to your organization for security and isolation\n\n### Authentication\n\nAll API endpoints require authentication via API key. Documentation endpoints (`/docs`, `/redoc`) and health checks (`/healthz`) are public.\n\n**Header:** `x-api-key` or `X-Api-Key`\n**Format:** Must start with `qg_` prefix\n**Example:** `qg_your_api_key_here`\n\nGet your API key from the [QualGent Dashboard](https://app.qualgent.ai/settings/api-keys)\n\n### Common Workflows\n\n#### Upload and Test an Application\n\n1. **Upload your app file:**\n   - For small files: `POST /v1/apps/upload`\n   - For large files: Use chunked upload flow\n     1. `POST /v1/apps/upload/init`\n     2. `POST /v1/apps/upload/chunk` (multiple times)\n     3. `POST /v1/apps/upload/finalize`\n\n2. **List your test cases:**\n   - `GET /v1/test-cases/list`\n\n3. **Queue a test run:**\n   - `POST /v1/test-cases/run`\n   - Provide: device config, app_file_id, test_case_id\n\n4. **Monitor test execution:**\n   - `GET /v1/jobs/list` - List all test runs\n   - `GET /v1/jobs/status/{test_run_id}` - Get specific test run status\n\n### Rate Limits\n\n- **API Rate Limit**: 10 requests per second per API key (default)\n- **Internal Hosts**: 30 requests per second for `app.qualgent.ai` and `app-staging.qualgent.ai`\n- Maximum 10 test runs per batch request\n- Chunked uploads stored in-memory (session-based)\n\n### Support\n\nFor assistance, contact [support@qualgent.ai](mailto:support@qualgent.ai) or visit [qualgent.ai](https://qualgent.ai)\n        ","version":"0.1.0"},"servers":[{"url":"https://api.qualgent.ai","description":"Production server"},{"url":"http://localhost:8081","description":"Local development server"}],"paths":{"/healthz":{"get":{"tags":["system"],"summary":"Health check","description":"Check if the API service is running and healthy. This endpoint does not require authentication.","operationId":"healthz_healthz_get","responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{},"example":{"status":"ok"}}}}}}},"/v1/apps/upload-test-file":{"post":{"tags":["apps"],"summary":"Upload a test-case attachment file","description":"Upload a supported test asset that can be attached to test cases via\n    `file_attachments` or legacy `file_ids`.\n\n    Supported formats: PDF, CSV, JSON, XML, TXT, PNG, JPG/JPEG, GIF, MP4, MP3.\n    Uploaded rows are persisted in `test_files` with `file_kind = 'test'`.","operationId":"upload_test_file_v1_apps_upload_test_file_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_test_file_v1_apps_upload_test_file_post"}}}},"responses":{"200":{"description":"Test file uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"400":{"description":"Unsupported test file type"},"401":{"description":"Invalid or missing API key"},"500":{"description":"Failed to upload to storage or persist metadata"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/apps/upload":{"post":{"tags":["apps"],"summary":"Upload application file","description":"Upload a mobile application file (APK/IPA/AAB) to QualGent.\n\n    **How it works:**\n    - Server detects file size as it streams in\n    - Files < 32MB: Processed directly (fast path)\n    - Files ≥ 32MB: Automatically chunked server-side (no client chunking needed)\n    - **AAB files**: Converted to universal APK before storage when conversion is enabled; only the APK is stored. If conversion fails, the AAB is stored as-is. Other file types are stored unchanged.\n\n    **Benefits:**\n    - Simple: Just upload like any normal file\n    - Works with standard multipart/form-data\n    - No client-side chunking logic required\n    - Compatible with curl, Postman, fetch, etc.\n\n    **Form Data:**\n    - `file`: The file to upload (multipart/form-data)\n    - `app_name`: Application name (required)\n    - `version`: Application version (required)\n    - `os`: (optional) Operating system name (auto-inferred from file extension if not provided)\n\n    **Storage Path Format:**\n    - `{user_id}/{timestamp}-{app_name}-{version}.{ext}` (ext is .apk when AAB was converted)","operationId":"upload_app_v1_apps_upload_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_app_v1_apps_upload_post"}}}},"responses":{"200":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"},"example":{"success":true,"file":{"id":"3f9a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c","file_path":"user123/1757339253852-myapp-1.0.0.apk","filename":"1757339253852-myapp-1.0.0.apk","original_name":"myapp.apk","file_size":524288000,"file_type":"application/vnd.android.package-archive","app_name":"myapp","version":"1.0.0","os":"Android","user_id":"user123","organization_id":"org456"}}}}},"401":{"description":"Invalid or missing API key"},"413":{"description":"File size exceeds maximum allowed"},"500":{"description":"Failed to upload to storage or persist metadata"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/apps/list":{"get":{"tags":["apps"],"summary":"List Files","description":"List all non-deleted files belonging to the authenticated user's organization.\nExcludes soft-deleted files (where deleted_at is not null).\nReturns empty dict {} if no results found, or array of files with id, name, version, os.\nNote: This endpoint does not include pre-signed download links. Use /v1/apps/{id} to get a single app with a download link.","operationId":"list_files_v1_apps_list_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object"},{"type":"array","items":{"type":"object"}}],"title":"Response List Files V1 Apps List Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/apps/{id}":{"get":{"tags":["apps"],"summary":"Get App","description":"Get a specific app by ID or the latest app belonging to the authenticated user's organization.\n- Use /{id} to get a specific app by ID\n- Use /latest to get the organization's latest uploaded app\nExcludes soft-deleted files (where deleted_at is not null).\nReturns the same data structure as /v1/apps/list but for a single app.","operationId":"get_app_v1_apps__id__get","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","title":"Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get App V1 Apps  Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/apps/delete":{"post":{"tags":["apps"],"summary":"Delete Files","description":"Soft delete one or more files by setting their deleted_at timestamp.\nOnly files belonging to the user's organization can be deleted.","operationId":"delete_files_v1_apps_delete_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteFilesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/test-cases/list":{"get":{"tags":["test-cases"],"summary":"List all test cases","description":"List all test cases belonging to your organization.\n\n    **Query Parameters:**\n    - `category` (optional): Filter test cases by category ID\n\n    **Returns:**\n    - Array of test cases with their category and latest completed run\n    - Empty object `{}` if no test cases found\n\n    **Includes:**\n    - Test case details (id, name, status)\n    - Category information (if assigned)\n    - Latest completed test run (if any)","operationId":"list_test_cases_v1_test_cases_list_get","parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved test cases","content":{"application/json":{"schema":{"anyOf":[{"type":"object"},{"type":"array","items":{"$ref":"#/components/schemas/TestCaseItem"}}],"title":"Response List Test Cases V1 Test Cases List Get"},"examples":{"with_data":{"value":[{"id":"660e8400-e29b-41d4-a716-446655440000","name":"Login Flow Test","status":"active","category":{"id":"cat-123","name":"Smoke Tests"}}]},"empty":{"value":{}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/test-cases":{"post":{"tags":["test-cases"],"summary":"Create a test case","description":"Create a new test case with an initial version snapshot.","operationId":"create_test_case_v1_test_cases_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestCaseRequest"}}}},"responses":{"201":{"description":"Test case created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestCaseResponse"}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/test-cases/{test_case_id}":{"get":{"tags":["test-cases"],"summary":"Get a test case","description":"Retrieve a single test case by ID, including version history and category. Pass `version_id` query param to get snapshotted fields from a specific version.","operationId":"get_test_case_v1_test_cases__test_case_id__get","parameters":[{"name":"test_case_id","in":"path","required":true,"schema":{"type":"string","title":"Test Case Id"}},{"name":"version_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Test case found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTestCaseResponse"}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Test case not found"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]},"patch":{"tags":["test-cases"],"summary":"Update a test case","description":"Partially update a test case. Automatically creates a new version snapshot.","operationId":"update_test_case_v1_test_cases__test_case_id__patch","parameters":[{"name":"test_case_id","in":"path","required":true,"schema":{"type":"string","title":"Test Case Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTestCaseRequest"}}}},"responses":{"200":{"description":"Test case updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTestCaseResponse"}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Test case not found"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]},"delete":{"tags":["test-cases"],"summary":"Delete a test case","description":"Soft-delete a test case by setting deleted_at timestamp.","operationId":"delete_test_case_v1_test_cases__test_case_id__delete","parameters":[{"name":"test_case_id","in":"path","required":true,"schema":{"type":"string","title":"Test Case Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Test case deleted","content":{"application/json":{"schema":{}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Test case not found"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/test-cases/run":{"post":{"tags":["test-cases"],"summary":"Queue test runs for execution","description":"Queue one or more test runs for execution on specified or auto-selected devices.\n\n    **Smart Defaults:**\n    - Specify `platform` (ios/android) to auto-select the most recent app file and default device\n    - Or provide explicit `app_file_id` and/or `device` configuration\n    - Platform is required if app_file_id or device is not specified\n\n    **Device / SIM:**\n    - Optional `device.sms_enabled` (true/false): set when the user picks an SMS-enabled device (e.g. from GET /v1/devices).\n    - Optional `use_sim` (true/false): use simulator/SIM for SMS; if omitted, derived from `device.sms_enabled`.\n\n    **Limits:**\n    - Minimum: 1 job per request\n\n    **Process:**\n    1. Validates that the requested device exists in the devices table with availability=True\n    2. Validates that test_case_id and app_file_id belong to your organization\n    3. Creates test_run records with 'queued' status\n    4. Creates job records for the test execution queue\n\n    **Returns:**\n    - Links to view each test run at https://app.qualgent.ai/test-runs/{id}\n\n    **Transaction Handling:**\n    - If any job fails, all jobs in the batch are rolled back\n    - Ensures data consistency across test_runs and test_jobs_pgqueuer tables","operationId":"run_test_v1_test_cases_run_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunTestRequest"}}}},"responses":{"200":{"description":"Test runs successfully queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunTestResponse"},"example":{"success":true,"jobs":[{"link":"https://app.qualgent.ai/test-runs/abc123"},{"link":"https://app.qualgent.ai/test-runs/def456"}]}}}},"400":{"description":"Bad request - empty jobs list, invalid device, invalid test_case_id/app_file_id, or invalid test_case_version_id","content":{"application/json":{"examples":{"empty_jobs":{"value":{"detail":"At least one job is required"}},"invalid_device":{"value":{"detail":"Device does not exist: {'name': 'iPhone 99', 'platform': 'iOS', 'os_version': '99'}"}},"invalid_test_case":{"value":{"detail":"Invalid test_case_id: 660e8400-e29b-41d4-a716-446655440000"}},"invalid_test_case_version_id":{"value":{"detail":"Invalid test_case_version: 770e8400-e29b-41d4-a716-446655440001 (not found in test_case_versions)"}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/test-cases/run-all":{"post":{"tags":["test-cases"],"summary":"Queue test runs for all test cases on a specific app file","description":"Queue test runs for ALL test cases in your organization on a specific application file.\n\n    **Process:**\n    1. Validates that the requested device exists in the devices table with availability=True\n    2. Validates that app_file_id belongs to your organization\n    3. Fetches all active test cases for your organization (optionally filtered by category)\n    4. Creates test_run records for each test case with 'queued' status\n    5. Creates job records for the test execution queue\n\n    **Category Filtering:**\n    - Optional: Provide category_id to run only test cases in a specific category\n    - If category_id is provided, only test cases in that category will be queued\n    - If no category_id is provided, all active test cases will be queued\n\n    **Returns:**\n    - Links to view each test run at https://app.qualgent.ai/test-runs/{id}\n    - Returns count of total test runs created\n\n    **Transaction Handling:**\n    - If any job fails, all jobs in the batch are rolled back\n    - Ensures data consistency across test_runs and test_jobs_pgqueuer tables\n\n    **Note:**\n    - If there are no test cases (or no test cases in the specified category), returns empty jobs array\n    - Only active test cases are included","operationId":"run_all_tests_v1_test_cases_run_all_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAllTestRequest"}}}},"responses":{"200":{"description":"Test runs successfully queued for all test cases","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunTestResponse"},"example":{"success":true,"jobs":[{"link":"https://app.qualgent.ai/test-runs/abc123"},{"link":"https://app.qualgent.ai/test-runs/def456"}]}}}},"400":{"description":"Bad request - invalid device or invalid app_file_id","content":{"application/json":{"examples":{"invalid_device":{"value":{"detail":"Device does not exist: {'name': 'iPhone 99', 'platform': 'iOS', 'os_version': '99'}"}},"invalid_app_file_id":{"value":{"detail":"Invalid app_file_id: 550e8400-e29b-41d4-a716-446655440000"}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/jobs/list":{"get":{"tags":["jobs"],"summary":"List all test runs","description":"List all test runs (jobs) for your organization.\n\n    **Returns:**\n    - Array of test runs with status and execution details\n    - Empty object `{}` if no test runs found\n\n    **Includes:**\n    - Test run status, priority, and progress\n    - Associated test case and application file\n    - Device and user execution details\n    - Direct link to view test run","operationId":"list_test_run_status_v1_jobs_list_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved test runs","content":{"application/json":{"schema":{"anyOf":[{"type":"object"},{"type":"array","items":{"$ref":"#/components/schemas/TestRunStatusItem"}}],"title":"Response List Test Run Status V1 Jobs List Get"},"examples":{"with_data":{"value":[{"id":"abc123","link":"https://app.qualgent.ai/test-runs/abc123","test_case":{"id":"tc1","name":"Login Test"},"app":{"id":"app1","name":"myapp.apk"},"status":"queued","priority":"medium","progress":0,"executed_by":{"id":"user1","name":"John Doe","email":"john@example.com"},"device":"iPhone 14 Pro"}]},"empty":{"value":{}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/jobs/status/{test_run_id}":{"get":{"tags":["jobs"],"summary":"Get test run status by ID","description":"Get detailed status information for a specific test run.\n\n    **Path Parameters:**\n    - `test_run_id`: The unique identifier of the test run\n\n    **Returns:**\n    - Detailed test run information including:\n        - Current status and progress\n        - Associated test case and application\n        - Device information\n        - User who initiated the test\n\n    **Authorization:**\n    - Only returns test runs belonging to your organization","operationId":"get_test_run_status_v1_jobs_status__test_run_id__get","parameters":[{"name":"test_run_id","in":"path","required":true,"schema":{"type":"string","title":"Test Run Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved test run details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRunDetailResponse"},"example":{"id":"abc123","test_case":{"id":"tc1","name":"Login Test"},"app":{"id":"app1","name":"myapp.apk"},"status":"running","priority":"medium","progress":45,"executed_by":{"id":"user1","name":"John Doe","email":"john@example.com"},"device":"iPhone 14 Pro"}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Test run not found or doesn't belong to your organization","content":{"application/json":{"example":{"message":"This ID doesn't exist in your organization."}}}},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/jobs/start":{"post":{"tags":["jobs"],"summary":"Start a new job/test run","description":"Submit a job to the queue by creating entries in both test_jobs_pgqueuer and test_runs tables.\n\n    **Process:**\n    1. Validates that test_case_id and app_file_id belong to your organization\n    2. Creates a test_run record with 'queued' status\n    3. Creates a job record for the test execution queue\n\n    **Returns:**\n    - Test run ID\n    - Job UUID\n    - Link to view test run at https://app.qualgent.ai/test-runs/{id}\n\n    **Transaction Handling:**\n    - If job creation fails after test_run creation, the test_run is rolled back\n    - Ensures data consistency across test_runs and test_jobs_pgqueuer tables","operationId":"start_job_v1_jobs_start_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartJobRequest"}}}},"responses":{"200":{"description":"Job successfully started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartJobResponse"},"example":{"success":true,"test_run_id":"abc123","job_id":"550e8400-e29b-41d4-a716-446655440000","link":"https://app.qualgent.ai/test-runs/abc123"}}}},"400":{"description":"Bad request - invalid test_case_id, app_file_id, or test_case_version_id","content":{"application/json":{"examples":{"invalid_test_case":{"value":{"detail":"Invalid test_case_id: 660e8400-e29b-41d4-a716-446655440000"}},"invalid_app_file":{"value":{"detail":"Invalid app_file_id: 550e8400-e29b-41d4-a716-446655440000"}},"invalid_test_case_version_id":{"value":{"detail":"Invalid test_case_version: 770e8400-e29b-41d4-a716-446655440001 (not found in test_case_versions)"}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/test-runs/{test_run_id}/steps":{"get":{"tags":["test-runs"],"summary":"Get sanitized step-by-step execution trace for a test run","description":"Return the ordered list of user-facing actions the agent performed during\n    a test run. Internal agent reasoning, supervisor bookkeeping, and raw\n    screenshot/overlay artifacts are filtered out — this is the same subset\n    the app.qualgent.ai test-run detail page renders as \"steps\".\n\n    **Path Parameters:**\n    - `test_run_id`: The unique identifier of the test run\n\n    **Returns:**\n    - Ordered array of step objects (`step_number`, `action`, `result`,\n      `error_message`, `timestamp`).\n    - Empty array if the run is queued/running with no steps recorded yet.\n\n    **Authorization:**\n    - Only returns steps for test runs belonging to your organization.","operationId":"get_test_run_steps_v1_test_runs__test_run_id__steps_get","parameters":[{"name":"test_run_id","in":"path","required":true,"schema":{"type":"string","title":"Test Run Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved test run steps","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TestRunStepItem"},"title":"Response Get Test Run Steps V1 Test Runs  Test Run Id  Steps Get"},"example":[{"step_number":1,"action":"Tapped at (540, 1944)","result":"success","timestamp":"2026-04-13T12:34:56.789Z"},{"step_number":2,"action":"Tapped at (210, 880)","result":"failed","error_message":"Element not found: 'Submit'","timestamp":"2026-04-13T12:35:02.145Z"}]}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Test run not found or doesn't belong to your organization"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/test-runs/{test_run_id}/plan":{"get":{"tags":["test-runs"],"summary":"Get the resolved test plan (with setup/act/verify kinds) for a test run","description":"Return the ordered plan steps that this run was queued against, with\n    QUA-2144 `[setup|act|verify]` kind tags surfaced as a structured field.\n\n    Variables (e.g. `{{username}}`) are already resolved — the strings here\n    are exactly what the agent saw. Pairs with `GET /v1/test-runs/{id}/steps`:\n    that endpoint says *what the agent did*, this one says *what it was\n    supposed to do and how each step was categorized*.\n\n    **Path Parameters:**\n    - `test_run_id`: The unique identifier of the test run\n\n    **Returns:**\n    - Ordered array of plan step objects (`step_number`, `kind`, `description`).\n    - Empty array if no plan is recoverable (e.g. legacy runs with neither\n      `resolved_steps` nor a pinned test_case_version).\n\n    **Authorization:**\n    - Only returns plans for test runs belonging to your organization.","operationId":"get_test_run_plan_v1_test_runs__test_run_id__plan_get","parameters":[{"name":"test_run_id","in":"path","required":true,"schema":{"type":"string","title":"Test Run Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved test run plan","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TestRunPlanStep"},"title":"Response Get Test Run Plan V1 Test Runs  Test Run Id  Plan Get"},"example":[{"step_number":1,"kind":"setup","description":"Open the app"},{"step_number":2,"kind":"act","description":"Tap \"Log In\""},{"step_number":3,"kind":"verify","description":"Verify \"Welcome back\" is visible"}]}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Test run not found or doesn't belong to your organization"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/categories/list":{"get":{"tags":["categories"],"summary":"List all categories","description":"List all categories belonging to your organization.\n\n    **Returns:**\n    - Array of categories with their details\n    - Empty object `{}` if no categories found\n\n    **Includes:**\n    - Category ID\n    - Category name\n    - Category description","operationId":"list_categories_v1_categories_list_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved categories","content":{"application/json":{"schema":{"anyOf":[{"type":"object"},{"type":"array","items":{"$ref":"#/components/schemas/CategoryItem"}}],"title":"Response List Categories V1 Categories List Get"},"examples":{"with_data":{"value":[{"id":"550e8400-e29b-41d4-a716-446655440000","name":"Smoke Tests","description":"Quick smoke tests to verify basic functionality"}]},"empty":{"value":{}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/devices":{"get":{"tags":["devices"],"summary":"List all available devices","description":"List all devices where availability is TRUE, grouped by device_type.\n\n    **Returns:**\n    - Dictionary grouped by device_type, where each key is a device_type and value is an array of devices\n    - Empty object `{}` if no devices found\n\n    **Includes:**\n    - Device name\n    - OS version\n    - Platform\n    - Device type\n    - sms_enabled","operationId":"list_devices_v1_devices_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved devices grouped by device_type","content":{"application/json":{"schema":{"type":"object","title":"Response List Devices V1 Devices Get"},"examples":{"with_data":{"value":{"phone":[{"name":"Pixel 7","os_version":"13.0","platform":"Android","sms_enabled":true}],"tablet":[{"name":"iPad Pro","os_version":"16.0","platform":"iOS","sms_enabled":false}]}},"empty":{"value":{}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/credentials/list":{"get":{"tags":["credentials"],"summary":"List credentials","description":"List all credentials belonging to your organization.\n\n    **Returns:**\n    - Object mapping credential ID to credential name\n    - Empty object `{}` if no credentials found\n\n    **Note:** Only credential IDs and names are returned. Actual secret values are never exposed.","operationId":"list_credentials_v1_credentials_list_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successfully retrieved credentials","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response List Credentials V1 Credentials List Get"},"examples":{"with_data":{"value":{"00000000-0000-0000-0000-000000000001":"stripe-test-key","00000000-0000-0000-0000-000000000002":"twilio-prod-token"}},"empty":{"value":{}}}}}},"401":{"description":"Invalid or missing API key"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/bugs/list":{"get":{"tags":["bugs"],"summary":"List bugs in your organization","description":"List bugs filed in the API key owner's organization. Filterable by source / status / app_file_id / failure_classification. Ordered by created_at DESC.\n\nReturns an empty list when no bugs match — note that QualGent3's parallel `/api/bugs` returns `{}` on empty; this endpoint does NOT match that quirk and always returns a list.","operationId":"list_bugs_v1_bugs_list_get","parameters":[{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by source (e.g. 'mcp', 'devloop').","title":"Source"},"description":"Filter by source (e.g. 'mcp', 'devloop')."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["open","in_progress","fixed","wont_fix"],"type":"string"},{"type":"null"}],"description":"Filter by status.","title":"Status"},"description":"Filter by status."},{"name":"failure_classification","in":"query","required":false,"schema":{"anyOf":[{"enum":["app_bug","setup_issue"],"type":"string"},{"type":"null"}],"description":"Filter by failure classification ('app_bug' or 'setup_issue').","title":"Failure Classification"},"description":"Filter by failure classification ('app_bug' or 'setup_issue')."},{"name":"app","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for app_file_id when listing candidate issues.","title":"App"},"description":"Alias for app_file_id when listing candidate issues."},{"name":"app_file_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by associated app file id.","title":"App File Id"},"description":"Filter by associated app file id."},{"name":"test_run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by metadata.test_run_id.","title":"Test Run Id"},"description":"Filter by metadata.test_run_id."},{"name":"test_case_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by metadata.test_case_id.","title":"Test Case Id"},"description":"Filter by metadata.test_case_id."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"},"title":"Response List Bugs V1 Bugs List Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/bugs":{"get":{"tags":["bugs"],"summary":"List bugs in your organization","description":"List bugs filed in the API key owner's organization. Filterable by source / status / app_file_id / failure_classification. Ordered by created_at DESC.\n\nWhen `app` or `app_file_id` is paired with `status=open`, this returns candidate issues for attaching a peer test run, scoped through the shared get_bug_test_run_candidate_issues RPC.","operationId":"list_bugs_v1_bugs_get","parameters":[{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by source (e.g. 'mcp', 'devloop').","title":"Source"},"description":"Filter by source (e.g. 'mcp', 'devloop')."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["open","in_progress","fixed","wont_fix"],"type":"string"},{"type":"null"}],"description":"Filter by status.","title":"Status"},"description":"Filter by status."},{"name":"failure_classification","in":"query","required":false,"schema":{"anyOf":[{"enum":["app_bug","setup_issue"],"type":"string"},{"type":"null"}],"description":"Filter by failure classification ('app_bug' or 'setup_issue').","title":"Failure Classification"},"description":"Filter by failure classification ('app_bug' or 'setup_issue')."},{"name":"app","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for app_file_id when listing candidate issues.","title":"App"},"description":"Alias for app_file_id when listing candidate issues."},{"name":"app_file_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by associated app file id.","title":"App File Id"},"description":"Filter by associated app file id."},{"name":"test_run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by metadata.test_run_id.","title":"Test Run Id"},"description":"Filter by metadata.test_run_id."},{"name":"test_case_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by metadata.test_case_id.","title":"Test Case Id"},"description":"Filter by metadata.test_case_id."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"},"title":"Response List Bugs V1 Bugs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]},"post":{"tags":["bugs"],"summary":"File a new bug","description":"Create a bug in the API key owner's organization. `source` is forced to `\"mcp\"` server-side; `user_id` / `organization_id` / `bug_id` are derived from the API key (callers cannot override).","operationId":"create_bug_v1_bugs_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBugBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Bug V1 Bugs Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/bugs/{bug_uuid}":{"get":{"tags":["bugs"],"summary":"Get a single bug","description":"Get a single bug by row id. Returns 404 when not found OR when the bug belongs to a different organization (no existence-leak).","operationId":"get_bug_v1_bugs__bug_uuid__get","parameters":[{"name":"bug_uuid","in":"path","required":true,"schema":{"type":"string","title":"Bug Uuid"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Bug V1 Bugs  Bug Uuid  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]},"patch":{"tags":["bugs"],"summary":"Update bug fields","description":"Sparse update for editable MCP bug fields: summary, steps, status, failure_classification, app_file_id, and metadata. Omitted fields stay unchanged. Explicit `app_file_id: null` clears the current app-build association; non-null app_file_id values must belong to the caller's organization and not be soft-deleted. Writes a `bug_status_changes` audit row with `source = \"mcp\"` only on real status transitions.","operationId":"update_bug_v1_bugs__bug_uuid__patch","parameters":[{"name":"bug_uuid","in":"path","required":true,"schema":{"type":"string","title":"Bug Uuid"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBugBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Update Bug V1 Bugs  Bug Uuid  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/bugs/{bug_uuid}/runs":{"post":{"tags":["bugs"],"summary":"Attach a test run to a bug","description":"Attach an additional test run to an existing bug through the shared link_bug_test_run RPC. The operation is idempotent and org-scoped.","operationId":"attach_bug_run_v1_bugs__bug_uuid__runs_post","parameters":[{"name":"bug_uuid","in":"path","required":true,"schema":{"type":"string","title":"Bug Uuid"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachBugRunBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Attach Bug Run V1 Bugs  Bug Uuid  Runs Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/bugs/{bug_uuid}/attachment/upload-url":{"post":{"tags":["bugs"],"summary":"Mint a signed upload URL for a bug attachment","description":"Returns a short-lived signed upload URL. Client PUTs the bytes directly to Supabase storage with `Content-Type` matching the body, then calls `/finalize`. Cap is 1 GB to match /v1/loops.","operationId":"create_attachment_upload_url_v1_bugs__bug_uuid__attachment_upload_url_post","parameters":[{"name":"bug_uuid","in":"path","required":true,"schema":{"type":"string","title":"Bug Uuid"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentUploadUrlBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Attachment Upload Url V1 Bugs  Bug Uuid  Attachment Upload Url Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/bugs/{bug_uuid}/attachment/finalize":{"post":{"tags":["bugs"],"summary":"Finalize a bug attachment after direct-to-storage upload","description":"Verify the uploaded object exists in storage and persist the attachment metadata on the bug row. Returns the updated bug.","operationId":"finalize_attachment_v1_bugs__bug_uuid__attachment_finalize_post","parameters":[{"name":"bug_uuid","in":"path","required":true,"schema":{"type":"string","title":"Bug Uuid"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentFinalizeBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Finalize Attachment V1 Bugs  Bug Uuid  Attachment Finalize Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}}},"components":{"schemas":{"AppFileReference":{"properties":{"id":{"type":"string","title":"Id","description":"Application file ID"},"app_name":{"type":"string","title":"App Name","description":"Application name"}},"type":"object","required":["id","app_name"],"title":"AppFileReference","description":"Reference to an application file"},"AppReference":{"properties":{"id":{"type":"string","title":"Id","description":"Application file ID"},"name":{"type":"string","title":"Name","description":"Original filename"}},"type":"object","required":["id","name"],"title":"AppReference","description":"Reference to an application file"},"AttachBugRunBody":{"properties":{"test_run_id":{"type":"string","minLength":1,"title":"Test Run Id"}},"additionalProperties":false,"type":"object","required":["test_run_id"],"title":"AttachBugRunBody"},"AttachmentFinalizeBody":{"properties":{"storage_path":{"type":"string","minLength":1,"title":"Storage Path"},"filename":{"type":"string","maxLength":512,"minLength":1,"title":"Filename"},"content_type":{"type":"string","maxLength":128,"minLength":1,"title":"Content Type"},"size":{"type":"integer","exclusiveMinimum":0.0,"title":"Size"}},"additionalProperties":false,"type":"object","required":["storage_path","filename","content_type","size"],"title":"AttachmentFinalizeBody"},"AttachmentUploadUrlBody":{"properties":{"filename":{"type":"string","maxLength":512,"minLength":1,"title":"Filename"},"content_type":{"type":"string","maxLength":128,"minLength":1,"title":"Content Type"},"size":{"type":"integer","exclusiveMinimum":0.0,"title":"Size","description":"File size in bytes; rejected above 1 GB."}},"additionalProperties":false,"type":"object","required":["filename","content_type","size"],"title":"AttachmentUploadUrlBody"},"Body_upload_app_v1_apps_upload_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"File to upload"},"app_name":{"type":"string","title":"App Name","description":"Application name"},"version":{"type":"string","title":"Version","description":"Application version"},"os":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os","description":"Operating system"},"package_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Package Name","description":"Package name (Android) or bundle ID (iOS)"}},"type":"object","required":["file","app_name","version"],"title":"Body_upload_app_v1_apps_upload_post"},"Body_upload_test_file_v1_apps_upload_test_file_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Supported test-case attachment file"}},"type":"object","required":["file"],"title":"Body_upload_test_file_v1_apps_upload_test_file_post"},"CategoryItem":{"properties":{"id":{"type":"string","title":"Id","description":"Category ID"},"name":{"type":"string","title":"Name","description":"Category name"},"description":{"type":"string","title":"Description","description":"Category description","default":""}},"type":"object","required":["id","name"],"title":"CategoryItem","description":"Category information"},"CategoryReference":{"properties":{"id":{"type":"string","title":"Id","description":"Category ID"},"name":{"type":"string","title":"Name","description":"Category name"}},"type":"object","required":["id","name"],"title":"CategoryReference","description":"Reference to a category"},"CreateBugBody":{"properties":{"summary":{"type":"string","maxLength":2000,"minLength":1,"title":"Summary"},"steps":{"type":"string","maxLength":20000,"title":"Steps","default":""},"status":{"type":"string","enum":["open","in_progress","fixed","wont_fix"],"title":"Status","default":"open"},"app_file_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"App File Id","description":"UUID of an uploaded app build in the caller's organization. 404 if not found or cross-org. Accepts any UUID version — Postgres' `uuid` column does too, and gen_random_uuid() happens to produce v4 but we don't want the API enforcing an implementation detail of the underlying RNG."},"test_case_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Case Id","description":"Written into metadata.test_case_id for downstream attribution."},"test_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Run Id","description":"Written into metadata.test_run_id for downstream attribution."},"failure_classification":{"type":"string","enum":["app_bug","setup_issue"],"title":"Failure Classification","description":"Classifies the issue as an app bug or setup issue.","default":"app_bug"},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["summary"],"title":"CreateBugBody","description":"Body for POST /v1/bugs.\n\n``source`` is intentionally NOT exposed — this router forces\n``source = \"mcp\"`` server-side. ``user_id`` / ``organization_id`` /\n``bug_id`` are likewise server-forced from the API key context."},"CreateTestCaseRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Test case name"},"steps":{"items":{"$ref":"#/components/schemas/TestStepInput"},"type":"array","minItems":1,"title":"Steps","description":"Ordered list of test steps"},"expected_result":{"type":"string","title":"Expected Result","description":"Expected result after executing steps"},"priority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority","description":"Priority level","default":"Medium","examples":["Low","Medium","High"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional description"},"category_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Id","description":"Category ID to assign"},"change_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Source","description":"Origin of the change (e.g. 'api', 'mcp')","default":"api"},"variables":{"anyOf":[{"items":{"$ref":"#/components/schemas/TestCaseVariableInput"},"type":"array"},{"type":"null"}],"title":"Variables","description":"Declared variable schema. Referenced in step text via `{{name}}`. Values supplied at run creation time. Omit or pass [] for no variables."},"file_attachments":{"anyOf":[{"items":{"$ref":"#/components/schemas/TestCaseFileAttachmentInput"},"type":"array"},{"type":"null"}],"title":"File Attachments","description":"UI-compatible test-file attachments with per-association descriptions."},"file_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"File Ids","description":"Legacy test-file attachment IDs. Used with null descriptions."}},"type":"object","required":["name","steps","expected_result"],"title":"CreateTestCaseRequest","description":"Request to create a new test case"},"CreateTestCaseResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Created test case ID"},"name":{"type":"string","title":"Name","description":"Test case name"},"version_id":{"type":"string","title":"Version Id","description":"Initial version ID"},"files":{"items":{"$ref":"#/components/schemas/TestCaseFileAttachmentOutput"},"type":"array","title":"Files","description":"Attached test files persisted for this test case."},"warnings":{"anyOf":[{"items":{"$ref":"#/components/schemas/VariableWarning"},"type":"array"},{"type":"null"}],"title":"Warnings","description":"Non-fatal signals about variables; absent when nothing to warn about."}},"type":"object","required":["id","name","version_id"],"title":"CreateTestCaseResponse","description":"Response after creating a test case"},"DeleteFilesRequest":{"properties":{"files":{"items":{"type":"string"},"type":"array","title":"Files"}},"type":"object","required":["files"],"title":"DeleteFilesRequest"},"ExecutedByUser":{"properties":{"id":{"type":"string","title":"Id","description":"User ID"},"name":{"type":"string","title":"Name","description":"User full name"},"email":{"type":"string","title":"Email","description":"User email address"}},"type":"object","required":["id","name","email"],"title":"ExecutedByUser","description":"User who executed the test run"},"FileMetadata":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Unique file record ID — pass as app_file_id on subsequent run submissions"},"file_path":{"type":"string","title":"File Path","description":"Storage path of uploaded file"},"filename":{"type":"string","title":"Filename","description":"Generated filename"},"original_name":{"type":"string","title":"Original Name","description":"Original uploaded filename"},"file_size":{"type":"integer","title":"File Size","description":"File size in bytes"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type","description":"MIME type"},"app_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Name","description":"Application name"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version","description":"Application version"},"os":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os","description":"Operating system"},"package_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Package Name","description":"Package name (Android) or bundle ID (iOS)"},"user_id":{"type":"string","title":"User Id","description":"User ID who uploaded the file"},"organization_id":{"type":"string","title":"Organization Id","description":"Organization ID"}},"type":"object","required":["file_path","filename","original_name","file_size","file_type","app_name","version","os","package_name","user_id","organization_id"],"title":"FileMetadata","description":"File metadata after upload"},"GetTestCaseResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"steps":{"type":"string","title":"Steps"},"expected_result":{"type":"string","title":"Expected Result"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"priority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"category":{"anyOf":[{"$ref":"#/components/schemas/CategoryReference"},{"type":"null"}]},"versions":{"items":{"$ref":"#/components/schemas/TestCaseVersionItem"},"type":"array","title":"Versions","default":[]},"variables":{"items":{"$ref":"#/components/schemas/TestCaseVariableOutput"},"type":"array","title":"Variables","description":"Declared variables on this test case. Empty array when none."},"files":{"items":{"$ref":"#/components/schemas/TestCaseFileAttachmentOutput"},"type":"array","title":"Files","description":"Attached test files with per-association descriptions."}},"type":"object","required":["id","name","steps","expected_result"],"title":"GetTestCaseResponse","description":"Full test case detail"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobRequest":{"properties":{"test_case_id":{"type":"string","title":"Test Case Id","description":"ID of the test case to execute","examples":["660e8400-e29b-41d4-a716-446655440000"]},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform","description":"Platform to use for auto-selecting app and device (ios/android). Required if app_file_id or device not specified.","examples":["ios","android"]},"app_file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App File Id","description":"ID of the uploaded application file to test. If not provided, uses most recent file for specified platform.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"device":{"anyOf":[{"$ref":"#/components/schemas/app__routers__test_cases__DeviceConfig"},{"type":"null"}],"description":"Device configuration for test execution. If not provided, uses default device for specified platform."},"execution_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Execution Mode","description":"Execution mode: 'agent' (AI-driven) or 'maestro' (replay cached YAML). Defaults to 'agent'.","default":"agent","examples":["agent","maestro"]},"test_case_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Case Version Id","description":"ID of the test case version (test_case_versions.id) to run. If provided, test_runs will store test_case_version_id and test_case_version_number.","examples":["770e8400-e29b-41d4-a716-446655440001"]},"use_sim":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Use Sim","description":"Whether to use simulator / SIM (e.g. for SMS). Can be derived from device.sms_enabled when not set.","examples":[true,false]},"audit_source_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audit Source Run Id","description":"Test run ID of a previously audited run; when set, audit feedback is injected into agent context on rerun.","examples":["4d91736b-0f1c-4534-8fd8-62a4496439ac"]},"additional_app_file_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Additional App File Ids","description":"IDs of additional app files for multi-app testing (max 3). Linked via test_run_additional_apps junction table.","examples":[["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"]]},"vars":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Vars","description":"Supplied values for the test case's declared variables. Keys must match variable names declared on the test case; values must match declared types. Unknown keys are ignored with a warning; missing required variables (with no default) cause a 400.","examples":[{"branch":"feature/login-fix","timeout_seconds":60}]}},"type":"object","required":["test_case_id"],"title":"JobRequest","description":"Request to queue a single test run"},"RunAllTestRequest":{"properties":{"app_file_id":{"type":"string","title":"App File Id","description":"ID of the uploaded application file to test against all test cases","examples":["550e8400-e29b-41d4-a716-446655440000"]},"device":{"$ref":"#/components/schemas/app__routers__test_cases__DeviceConfig"},"category_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Id","description":"Optional category ID to filter test cases. If provided, only test cases in this category will be run.","examples":["770e8400-e29b-41d4-a716-446655440000"]},"execution_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Execution Mode","description":"Execution mode: 'agent' (AI-driven) or 'maestro' (replay cached YAML). Defaults to 'agent'. Tests without cached YAML will automatically fallback to agent mode.","default":"agent","examples":["agent","maestro"]}},"type":"object","required":["app_file_id","device"],"title":"RunAllTestRequest","description":"Request to queue test runs for all test cases on a specific app file"},"RunTestRequest":{"properties":{"jobs":{"items":{"$ref":"#/components/schemas/JobRequest"},"type":"array","minItems":1,"title":"Jobs","description":"List of test run jobs to queue","examples":[[{"app_file_id":"550e8400-e29b-41d4-a716-446655440000","device":{"name":"iPhone 14 Pro","orientation":"portrait","os_version":"17.0","platform":"iOS","sms_enabled":true},"test_case_id":"660e8400-e29b-41d4-a716-446655440000","use_sim":true}]]}},"type":"object","required":["jobs"],"title":"RunTestRequest","description":"Request to queue one or more test runs"},"RunTestResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the operation succeeded","examples":[true]},"jobs":{"items":{"$ref":"#/components/schemas/TestRunLink"},"type":"array","title":"Jobs","description":"Links to created test runs"},"warnings":{"anyOf":[{"items":{"$ref":"#/components/schemas/VariableWarning"},"type":"array"},{"type":"null"}],"title":"Warnings","description":"Non-fatal signals about variables supplied on the run. Currently used to report supplied keys that are not declared on the test case and were ignored."}},"type":"object","required":["success","jobs"],"title":"RunTestResponse","description":"Response after queuing test runs"},"StartJobRequest":{"properties":{"test_case_id":{"type":"string","title":"Test Case Id","description":"Test case ID to execute"},"app_file_id":{"type":"string","title":"App File Id","description":"Application file ID to test"},"device":{"$ref":"#/components/schemas/app__routers__jobs__DeviceConfig"},"priority":{"type":"string","title":"Priority","description":"Test run priority","default":"medium","examples":["low","medium","high"]},"test_case_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Case Version Id","description":"ID of the test case version (test_case_versions.id) to run. If provided, test_runs will store test_case_version_id and test_case_version_number.","examples":["770e8400-e29b-41d4-a716-446655440001"]},"use_sim":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Use Sim","description":"Whether to use simulator / SIM (e.g. for SMS). Can be derived from device.sms_enabled when not set.","examples":[true,false]},"vars":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Vars","description":"Supplied values for the test case's declared variables. Keys must match variable names declared on the test case; values must match declared types.","examples":[{"branch":"feature/login-fix","timeout_seconds":60}]}},"type":"object","required":["test_case_id","app_file_id","device"],"title":"StartJobRequest","description":"Request to start a new job/test run"},"StartJobResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the job was successfully started"},"test_run_id":{"type":"string","title":"Test Run Id","description":"Created test run ID"},"job_id":{"type":"string","title":"Job Id","description":"Created job UUID"},"link":{"type":"string","title":"Link","description":"URL to view test run details","examples":["https://app.qualgent.ai/test-runs/abc123"]}},"type":"object","required":["success","test_run_id","job_id","link"],"title":"StartJobResponse","description":"Response after starting a job"},"TestCaseFileAttachmentInput":{"properties":{"file_id":{"type":"string","minLength":1,"title":"File Id","description":"ID of a test_files row with file_kind = 'test'"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"Optional per-test-case description for this attachment."}},"type":"object","required":["file_id"],"title":"TestCaseFileAttachmentInput","description":"UI-compatible file attachment association for a test case."},"TestCaseFileAttachmentOutput":{"properties":{"file_id":{"type":"string","title":"File Id","description":"Attached test_files.id"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Per-association attachment description"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path","description":"Storage path for the attached test file"},"original_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Original Name","description":"Original uploaded filename"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type","description":"MIME type"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size","description":"File size in bytes"}},"type":"object","required":["file_id"],"title":"TestCaseFileAttachmentOutput","description":"Attached test file metadata returned from test-case endpoints."},"TestCaseItem":{"properties":{"id":{"type":"string","title":"Id","description":"Test case ID"},"name":{"type":"string","title":"Name","description":"Test case name"},"status":{"type":"string","title":"Status","description":"Test case status","examples":["active","inactive"]},"category":{"anyOf":[{"$ref":"#/components/schemas/CategoryReference"},{"type":"null"}],"description":"Category this test case belongs to"},"latest_completed_run":{"anyOf":[{"$ref":"#/components/schemas/TestRunItem"},{"type":"null"}],"description":"Most recent completed test run"}},"type":"object","required":["id","name","status"],"title":"TestCaseItem","description":"Test case with latest completed run"},"TestCaseReference":{"properties":{"id":{"type":"string","title":"Id","description":"Test case ID"},"name":{"type":"string","title":"Name","description":"Test case name"}},"type":"object","required":["id","name"],"title":"TestCaseReference","description":"Reference to a test case"},"TestCaseVariableInput":{"properties":{"name":{"type":"string","title":"Name","description":"Identifier matching /^[a-z][a-z0-9_]*$/, up to 40 chars; unique within the test case","examples":["branch","timeout_seconds"]},"type":{"type":"string","title":"Type","description":"One of \"string\" or \"number\"","examples":["string","number"]},"required":{"type":"boolean","title":"Required","description":"When true, callers must supply a value at run creation (unless a default is set). The frontend also prompts for this variable in the run form.","default":false},"default":{"anyOf":[{},{"type":"null"}],"title":"Default","description":"Value used when the caller does not supply one. Must match `type` if provided."},"description":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Description","description":"Optional human-readable description shown in tooltips and the run form."}},"type":"object","required":["name","type"],"title":"TestCaseVariableInput","description":"Declared variable on a test case.\n\nReferenced inside step text via `{{name}}` tokens; values are supplied at\nrun creation time via `vars` on POST /test-cases/run."},"TestCaseVariableOutput":{"properties":{"name":{"type":"string","title":"Name","description":"Identifier matching /^[a-z][a-z0-9_]*$/, up to 40 chars; unique within the test case","examples":["branch","timeout_seconds"]},"type":{"type":"string","title":"Type","description":"One of \"string\" or \"number\"","examples":["string","number"]},"required":{"type":"boolean","title":"Required","description":"When true, callers must supply a value at run creation (unless a default is set). The frontend also prompts for this variable in the run form.","default":false},"default":{"anyOf":[{},{"type":"null"}],"title":"Default","description":"Value used when the caller does not supply one. Must match `type` if provided."},"description":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Description","description":"Optional human-readable description shown in tooltips and the run form."}},"type":"object","required":["name","type"],"title":"TestCaseVariableOutput","description":"Declared variable as returned in read responses. Same shape as input."},"TestCaseVersionItem":{"properties":{"id":{"type":"string","title":"Id","description":"Version ID"},"version_number":{"type":"integer","title":"Version Number","description":"Version number"},"created_at":{"type":"string","title":"Created At","description":"Version creation timestamp"}},"type":"object","required":["id","version_number","created_at"],"title":"TestCaseVersionItem","description":"Version history entry"},"TestRunDetailResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Test run ID"},"test_case":{"anyOf":[{"$ref":"#/components/schemas/TestCaseReference"},{"type":"null"}],"description":"Associated test case"},"app":{"anyOf":[{"$ref":"#/components/schemas/AppReference"},{"type":"null"}],"description":"Application file being tested"},"status":{"type":"string","title":"Status","description":"Test run status","examples":["queued","running","passed","failed"]},"priority":{"type":"string","title":"Priority","description":"Test run priority","examples":["low","medium","high"]},"progress":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Progress","description":"Test run progress percentage","default":0,"examples":[0,50,100]},"executed_by":{"anyOf":[{"$ref":"#/components/schemas/ExecutedByUser"},{"type":"null"}],"description":"User who created this test run"},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device","description":"Device name","examples":["iPhone 14 Pro","simulator"]},"duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Seconds","description":"Approximate test run duration in seconds (created_at → updated_at or agent-reported).","examples":[123.45]},"steps":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steps","description":"Number of agent steps / actions in the run, if recorded.","examples":[42]},"credits_used":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Credits Used","description":"Credits consumed by this test run, if credit tracking is enabled.","examples":[5.0]},"cost_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Usd","description":"Approximate USD cost for this test run, derived from credits_used.","examples":[0.0234]},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Model identifier used for this run (e.g. openai/gpt-5-2025-08-07).","examples":["openai/gpt-5-2025-08-07"]},"vars":{"type":"object","title":"Vars","description":"Effective variable values for this run (supplied values merged over declared defaults). Empty when the test case had no variables.","examples":[{"branch":"feature/login-fix","timeout_seconds":60}]},"resolved_steps":{"items":{"type":"string"},"type":"array","title":"Resolved Steps","description":"Step descriptions after `{{name}}` substitution — the exact text the agent executed. Empty for runs created before the variables feature shipped.","examples":[["Tap the codepush build feature/login-fix button","Wait 60s for download"]]}},"type":"object","required":["id","test_case","app","status","priority","executed_by"],"title":"TestRunDetailResponse","description":"Detailed test run information"},"TestRunItem":{"properties":{"id":{"type":"string","title":"Id","description":"Test run ID"},"link":{"type":"string","title":"Link","description":"URL to view test run details","examples":["https://app.qualgent.ai/test-runs/123"]},"app":{"anyOf":[{"$ref":"#/components/schemas/AppFileReference"},{"type":"null"}],"description":"Application file being tested"},"status":{"type":"string","title":"Status","description":"Test run status","examples":["queued","running","passed","failed"]},"priority":{"type":"string","title":"Priority","description":"Test run priority","examples":["low","medium","high"]},"progress":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Progress","description":"Test run progress percentage","default":0,"examples":[0,50,100]},"executed_by":{"anyOf":[{"$ref":"#/components/schemas/ExecutedByUser"},{"type":"null"}],"description":"User who created this test run"},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device","description":"Device name","examples":["iPhone 14 Pro","simulator"]}},"type":"object","required":["id","link","app","status","priority","executed_by"],"title":"TestRunItem","description":"Test run information for latest completed run"},"TestRunLink":{"properties":{"link":{"type":"string","title":"Link","description":"URL to view test run","examples":["https://app.qualgent.ai/test-runs/123"]}},"type":"object","required":["link"],"title":"TestRunLink","description":"Link to a created test run"},"TestRunPlanStep":{"properties":{"step_number":{"type":"integer","title":"Step Number","description":"1-indexed position in the plan","examples":[1,2,3]},"kind":{"anyOf":[{"type":"string","enum":["setup","act","verify"]},{"type":"null"}],"title":"Kind","description":"Step category from the QUA-2144 `[setup|act|verify]` tag. Null when the author didn't tag the step (intentionally distinct from `\"act\"` so downstream classifiers can tell the cases apart).","examples":["setup","act","verify",null]},"description":{"type":"string","title":"Description","description":"Step description with variables resolved.","examples":["Open the app","Tap \"Log In\"","Verify \"Welcome back\" is visible"]}},"type":"object","required":["step_number","description"],"title":"TestRunPlanStep","description":"Single step from the test plan that was pinned to this run.\n\nSourced from `test_runs.resolved_steps` (variable-resolved + kind-tagged\nper QUA-2144), with fallback to the run's pinned `test_case_versions.steps`\nwhen `resolved_steps` is empty (legacy rows pre-QUA-2067 / older runs).\n\nPairs with the trace returned by `GET /v1/test-runs/{id}/steps`: that\nendpoint says *what the agent did*, this one says *what it was supposed\nto do and how each step was categorized*. MCP `run-and-analyze` uses\n`kind` to interpret a failure (verify-fail ⇒ real bug, setup-fail ⇒\ntest broken)."},"TestRunStatusItem":{"properties":{"id":{"type":"string","title":"Id","description":"Test run ID"},"link":{"type":"string","title":"Link","description":"URL to view test run details","examples":["https://app.qualgent.ai/test-runs/123"]},"test_case":{"anyOf":[{"$ref":"#/components/schemas/TestCaseReference"},{"type":"null"}],"description":"Associated test case"},"app":{"anyOf":[{"$ref":"#/components/schemas/AppReference"},{"type":"null"}],"description":"Application file being tested"},"status":{"type":"string","title":"Status","description":"Test run status","examples":["queued","running","passed","failed"]},"priority":{"type":"string","title":"Priority","description":"Test run priority","examples":["low","medium","high"]},"progress":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Progress","description":"Test run progress percentage","default":0,"examples":[0,50,100]},"executed_by":{"anyOf":[{"$ref":"#/components/schemas/ExecutedByUser"},{"type":"null"}],"description":"User who created this test run"},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device","description":"Device name","examples":["iPhone 14 Pro","simulator"]}},"type":"object","required":["id","link","test_case","app","status","priority","executed_by"],"title":"TestRunStatusItem","description":"Test run status information"},"TestRunStepItem":{"properties":{"step_number":{"type":"integer","title":"Step Number","description":"1-indexed position of this step in the sanitized trace","examples":[1,2,3]},"action":{"type":"string","title":"Action","description":"Human-readable description of what the agent did","examples":["Tapped at (540, 1944)","Typed: user@example.com","Swiped from (100, 500) to (100, 200)"]},"result":{"type":"string","title":"Result","description":"Outcome of the step","examples":["success","failed"]},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Error text if the step failed; null otherwise.","examples":["Element not found: 'Submit'"]},"timestamp":{"type":"string","title":"Timestamp","description":"ISO-8601 timestamp when the step was recorded.","examples":["2026-04-13T12:34:56.789Z"]}},"type":"object","required":["step_number","action","result","timestamp"],"title":"TestRunStepItem","description":"Single sanitized step from a test run's execution trace.\n\nRaw entries in `test_runs.result` include planner/executor/supervisor\ninternals (type=\"agent\", type=\"metric\"), screenshots, and overlays. This\nmodel exposes only the user-facing actions the agent took on the device —\nthe same subset the frontend renders as \"steps\"."},"TestStepInput":{"properties":{"description":{"type":"string","minLength":1,"title":"Description","description":"Single atomic action"},"credential_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Id","description":"Credential UUID if step needs auth"},"kind":{"anyOf":[{"type":"string","enum":["setup","act","verify"]},{"type":"null"}],"title":"Kind","description":"Phase of the test the step belongs to: 'setup' (preconditions, navigation to the screen under test), 'act' (the action being tested), or 'verify' (post-action assertion). Untagged steps are stored without a kind and are treated as untagged downstream.","examples":["setup","act","verify"]}},"type":"object","required":["description"],"title":"TestStepInput","description":"A single atomic test step."},"UpdateBugBody":{"properties":{"summary":{"anyOf":[{"type":"string","maxLength":2000,"minLength":1},{"type":"null"}],"title":"Summary"},"steps":{"anyOf":[{"type":"string","maxLength":20000},{"type":"null"}],"title":"Steps"},"status":{"anyOf":[{"type":"string","enum":["open","in_progress","fixed","wont_fix"]},{"type":"null"}],"title":"Status"},"failure_classification":{"anyOf":[{"type":"string","enum":["app_bug","setup_issue"]},{"type":"null"}],"title":"Failure Classification","description":"Classifies the issue as an app bug or setup issue."},"app_file_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"App File Id","description":"UUID of an uploaded app build in the caller's organization. Explicit null clears the current app-build association."},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata"}},"additionalProperties":false,"type":"object","title":"UpdateBugBody","description":"Sparse body for PATCH /v1/bugs/{id}.\n\nOmitted fields stay unchanged. Explicit ``app_file_id: null`` clears the\napp-build association; non-null values are org-scoped before write."},"UpdateTestCaseRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Test case name"},"steps":{"anyOf":[{"items":{"$ref":"#/components/schemas/TestStepInput"},"type":"array","minItems":1},{"type":"null"}],"title":"Steps","description":"Ordered list of test steps"},"expected_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Result","description":"Expected result"},"priority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority","description":"Priority level"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Description"},"change_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Source","description":"Origin of the change (e.g. 'api', 'mcp')","default":"api"},"variables":{"anyOf":[{"items":{"$ref":"#/components/schemas/TestCaseVariableInput"},"type":"array"},{"type":"null"}],"title":"Variables","description":"Replace the declared variable schema for this test case. Pass [] to clear all. Omit the field entirely to leave the existing schema unchanged."},"file_attachments":{"anyOf":[{"items":{"$ref":"#/components/schemas/TestCaseFileAttachmentInput"},"type":"array"},{"type":"null"}],"title":"File Attachments","description":"Replace attached test files with UI-compatible associations. Pass [] to clear. Omit the field to leave attachments unchanged."},"file_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"File Ids","description":"Legacy replacement list of test-file IDs. Pass [] to clear; omit to leave unchanged."}},"type":"object","title":"UpdateTestCaseRequest","description":"Partial update for a test case"},"UpdateTestCaseResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Test case ID"},"version_number":{"type":"integer","title":"Version Number","description":"New version number"},"version_id":{"type":"string","title":"Version Id","description":"New version ID"},"files":{"items":{"$ref":"#/components/schemas/TestCaseFileAttachmentOutput"},"type":"array","title":"Files","description":"Attached test files persisted for this test case."},"warnings":{"anyOf":[{"items":{"$ref":"#/components/schemas/VariableWarning"},"type":"array"},{"type":"null"}],"title":"Warnings","description":"Non-fatal signals about variables; absent when nothing to warn about."}},"type":"object","required":["id","version_number","version_id"],"title":"UpdateTestCaseResponse","description":"Response after updating a test case"},"UploadResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether upload succeeded","examples":[true]},"file":{"$ref":"#/components/schemas/FileMetadata","description":"Uploaded file metadata"}},"type":"object","required":["success","file"],"title":"UploadResponse","description":"Response after successful file upload"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VariableWarning":{"properties":{"code":{"type":"string","title":"Code","description":"One of: 'undeclared_reference' (step references a name not declared), 'literal_collision' (declaring a new variable turns existing literal `{{x}}` text into a substitution), 'unknown_variable' (run supplied a variable not declared on the test case; dropped silently).","examples":["undeclared_reference","unknown_variable"]},"message":{"type":"string","title":"Message","description":"Human-readable explanation"}},"type":"object","required":["code","message"],"title":"VariableWarning","description":"Non-fatal signal from write or run creation endpoints when variables are involved."},"app__routers__jobs__DeviceConfig":{"properties":{"name":{"type":"string","title":"Name","description":"Device name or identifier","examples":["iPhone 14 Pro","Pixel 7","simulator"]},"platform":{"type":"string","title":"Platform","description":"Device platform","examples":["iOS","Android"]},"os_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os Version","description":"Operating system version","examples":["17.0","13","14.0"]},"sms_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Sms Enabled","description":"Whether the device has SMS capability (e.g. for OTP). Set when user picks an SMS-enabled device.","examples":[true,false]}},"type":"object","required":["name","platform"],"title":"DeviceConfig","description":"Device configuration for test execution"},"app__routers__test_cases__DeviceConfig":{"properties":{"name":{"type":"string","title":"Name","description":"Device name or identifier","examples":["iPhone 14 Pro","Pixel 7","simulator"]},"platform":{"type":"string","title":"Platform","description":"Device platform","examples":["iOS","Android"]},"os_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os Version","description":"Operating system version","examples":["17.0","13","14.0"]},"orientation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orientation","description":"Device orientation","default":"portrait","examples":["portrait","landscape"]},"sms_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Sms Enabled","description":"Whether the device has SMS capability (e.g. for OTP). Set when user picks an SMS-enabled device.","examples":[true,false]}},"type":"object","required":["name","platform"],"title":"DeviceConfig","description":"Device configuration for test execution"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"API key for authentication. Must start with 'qg_' prefix. Get your key from https://app.qualgent.ai/settings/api-keys"}}},"tags":[{"name":"apps","description":"App upload operations for mobile applications (APK/IPA). Supports both single and chunked uploads."},{"name":"test-cases","description":"Test case management and execution. Queue and manage automated test runs."},{"name":"jobs","description":"Test run status and monitoring. Track execution progress and results."},{"name":"categories","description":"Category management. List and organize test cases by category."},{"name":"devices","description":"Device management. List available devices for test execution."},{"name":"credentials","description":"Credential management. List credentials stored in your organization."},{"name":"bugs","description":"Bug tracker for QualGent-MCP. List, file, attach to, and status-update bugs in your organization. Bugs filed via this surface carry `source = 'mcp'`."},{"name":"system","description":"System health and status endpoints."}]}