{"openapi":"3.1.0","info":{"title":"Empirical API","version":"1.0.0","description":"Programmatic access to Empirical test runs, snoozes, and more.","contact":{"name":"Empirical","url":"https://empirical.run"}},"servers":[{"url":"https://api.empirical.run"}],"security":[{"Bearer":[]}],"tags":[{"name":"Test Runs","description":"Trigger, list, and inspect end-to-end test runs."},{"name":"Test Cases","description":"Browse the Playwright test cases synced from a project's repository and manage their tags."},{"name":"Environments","description":"Named deployment targets (slug + Playwright project filters) a project's tests run against."},{"name":"Snoozes","description":"Temporarily suppress known test failures for specific Playwright tests."},{"name":"Analytics","description":"Aggregated test-run, test-count and test-case analytics for a project."},{"name":"Resources","description":"Project file resources — uploads stored in R2 and external link-type files (e.g. Google Sheets)."},{"name":"Badges","description":"Public README status badge images (SVG) for project environments."}],"components":{"securitySchemes":{"Bearer":{"type":"http","scheme":"bearer"}},"schemas":{"ErrorResponse":{"type":"object","properties":{"data":{"nullable":true,"description":"Always null for error responses."},"error":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable error message."}},"required":["message"],"description":"Error details."}},"required":["data","error"]},"Pagination":{"type":"object","properties":{"page":{"type":"integer","description":"Current page (1-indexed)."},"per_page":{"type":"integer","description":"Items per page."},"total":{"type":"integer","description":"Total number of items."},"total_pages":{"type":"integer","description":"Total number of pages."}},"required":["page","per_page","total","total_pages"],"description":"Pagination metadata."},"SnoozeListResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"snoozes":{"type":"array","items":{"$ref":"#/components/schemas/Snooze"}}},"required":["snoozes"],"description":"The page of snoozes."},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Snooze":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the snooze."},"test_ids":{"type":"array","items":{"type":"string"},"description":"Array of Playwright test IDs (41-character hashes) that are snoozed."},"snooze_until":{"type":"string","description":"ISO 8601 timestamp for when the snooze expires. Active when in the future."},"description":{"type":"string","nullable":true,"description":"Description of why the tests are snoozed."},"created_from_test_run_id":{"type":"number","nullable":true,"description":"ID of the test run that triggered the snooze creation, if applicable."},"created_by":{"type":"string","nullable":true,"description":"User ID of the creator. Set automatically for dashboard users; `null` for API-created snoozes."},"created_at":{"type":"string","description":"ISO 8601 timestamp of when the snooze was created."},"updated_at":{"type":"string","description":"ISO 8601 timestamp of when the snooze was last updated."},"scoped_to_environment_id":{"type":"number","nullable":true,"description":"Environment ID this snooze is scoped to. `null` if it applies to all environments."}},"required":["id","test_ids","snooze_until","description","created_from_test_run_id","created_by","created_at","updated_at","scoped_to_environment_id"]},"SnoozeResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"snooze":{"$ref":"#/components/schemas/Snooze"}},"required":["snooze"],"description":"Response payload."}},"required":["data"]},"CreateSnoozeRequest":{"type":"object","properties":{"test_ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Array of Playwright test IDs (41-character hashes) to snooze."},"snooze_until":{"type":"string","description":"ISO 8601 timestamp for when the snooze should expire."},"description":{"type":"string","description":"Optional description explaining why the tests are being snoozed."},"created_by":{"type":"string","description":"Optional user identifier for the snooze creator. Set automatically for dashboard users."},"scoped_to_environment_id":{"type":"number","description":"Environment ID to scope the snooze to. If omitted, applies to all environments."},"created_from_test_run_id":{"anyOf":[{"type":"string"},{"type":"number"}],"description":"ID of the test run this snooze was created from, if applicable."}},"required":["test_ids","snooze_until"]},"UpdateSnoozeRequest":{"type":"object","properties":{"snooze_until":{"type":"string","description":"ISO 8601 timestamp for when the snooze should expire."},"description":{"type":"string","nullable":true,"description":"Description, or `null` to clear it."},"expire_now":{"type":"boolean","description":"Set to `true` to immediately expire the snooze."},"scoped_to_environment_id":{"type":"number","nullable":true,"description":"Environment ID to scope the snooze to. Set to `null` to apply to all environments."},"test_ids":{"type":"array","items":{"type":"string"},"description":"Updated array of Playwright test IDs to snooze."}}},"AddSnoozeTestCasesRequest":{"type":"object","properties":{"test_ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Playwright test IDs to add to the snooze."}},"required":["test_ids"]},"ListEnvironmentsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"environments":{"type":"array","items":{"$ref":"#/components/schemas/Environment"}}},"required":["environments"],"description":"Response payload."}},"required":["data"]},"Environment":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier."},"project_id":{"type":"number","description":"Project identifier."},"slug":{"type":"string","description":"URL-friendly identifier."},"name":{"type":"string","description":"Display name."},"playwright_projects_match":{"type":"array","items":{"type":"string"},"description":"Playwright project names to match."},"playwright_projects_ignore":{"type":"array","items":{"type":"string"},"description":"Playwright project names to ignore."},"is_disabled":{"type":"boolean","nullable":true,"description":"Whether the environment is disabled."},"env_files":{"type":"array","items":{"type":"string"},"description":"Environment file paths."},"scheduled_trigger":{"type":"string","nullable":true,"description":"Cron expression for scheduled triggers."},"created_at":{"type":"string","nullable":true,"description":"ISO 8601 timestamp."},"updated_at":{"type":"string","nullable":true,"description":"ISO 8601 timestamp."}},"required":["id","project_id","slug","name","playwright_projects_match","playwright_projects_ignore","is_disabled","env_files","scheduled_trigger","created_at","updated_at"]},"EnvironmentResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"environment":{"$ref":"#/components/schemas/Environment"}},"required":["environment"],"description":"Response payload."}},"required":["data"]},"CreateEnvironmentRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"Display name."},"slug":{"type":"string","minLength":1,"description":"URL-friendly identifier; unique within the project."},"playwright_projects_match":{"type":"array","items":{"type":"string"},"description":"Playwright project names to include. Items must not start with '!'."},"playwright_projects_ignore":{"type":"array","items":{"type":"string"},"description":"Playwright project names to exclude."},"scheduled_trigger":{"type":"string","nullable":true,"description":"Cron expression for scheduled triggers, or null."}},"required":["name","slug"]},"EnvironmentSyncResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean","description":"Always true on success."}},"required":["success"],"description":"Response payload."}},"required":["data"]},"EnvironmentMutationError":{"type":"object","properties":{"error":{"anyOf":[{"type":"string"},{"type":"object","properties":{"message":{"type":"string","description":"Error message."}},"required":["message"]}],"description":"Error, returned as a bare string or a `{ message }` object (legacy shape)."}},"required":["error"]},"UpdateEnvironmentRequest":{"type":"object","properties":{"name":{"type":"string","description":"Display name."},"slug":{"type":"string","description":"URL-friendly identifier."},"is_disabled":{"type":"boolean","description":"Whether the environment is disabled."},"playwright_projects_match":{"type":"array","items":{"type":"string"},"description":"Playwright project names to include. Items must not start with '!'."},"playwright_projects_ignore":{"type":"array","items":{"type":"string"},"description":"Playwright project names to exclude."},"scheduled_trigger":{"type":"string","nullable":true,"description":"Cron expression for scheduled triggers, or null."}}},"EnvironmentDeleteResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Always true on success."}},"required":["success"]},"ProjectResourceListResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"resources":{"type":"array","items":{"$ref":"#/components/schemas/Resource"}}},"required":["resources"],"description":"The page of resources."},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Resource":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier."},"name":{"type":"string","description":"File name."},"url":{"type":"string","description":"Download URL. For uploaded files, a signed URL that expires in 1 hour. For link-type files, the stored external URL."},"description":{"type":"string","nullable":true,"description":"Resource description."},"mime_type":{"type":"string","nullable":true,"description":"MIME type."},"size_bytes":{"type":"number","nullable":true,"description":"File size in bytes."},"status":{"type":"string","enum":["pending","uploaded"],"description":"`pending` until confirmed via PATCH, then `uploaded`."},"access_level":{"type":"string","nullable":true,"description":"For link-type files (e.g. Google Sheets): \"read_write\", \"read\", or \"none\". Null for uploaded files."},"created_by":{"type":"string","nullable":true,"description":"User ID of the creator."},"created_at":{"type":"string","description":"ISO 8601 timestamp."},"updated_at":{"type":"string","description":"ISO 8601 timestamp."}},"required":["id","name","url","description","mime_type","size_bytes","status","access_level","created_by","created_at","updated_at"]},"CreateProjectResourceResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"resource":{"$ref":"#/components/schemas/ResourceWithUploadUrl"}},"required":["resource"],"description":"Response payload."}},"required":["data"]},"ResourceWithUploadUrl":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier."},"name":{"type":"string","description":"File name."},"url":{"type":"string","description":"Download URL. For uploaded files, a signed URL that expires in 1 hour. For link-type files, the stored external URL."},"description":{"type":"string","nullable":true,"description":"Resource description."},"mime_type":{"type":"string","nullable":true,"description":"MIME type."},"size_bytes":{"type":"number","nullable":true,"description":"File size in bytes."},"status":{"type":"string","enum":["pending","uploaded"],"description":"`pending` until confirmed via PATCH, then `uploaded`."},"access_level":{"type":"string","nullable":true,"description":"For link-type files (e.g. Google Sheets): \"read_write\", \"read\", or \"none\". Null for uploaded files."},"created_by":{"type":"string","nullable":true,"description":"User ID of the creator."},"created_at":{"type":"string","description":"ISO 8601 timestamp."},"updated_at":{"type":"string","description":"ISO 8601 timestamp."},"upload_url":{"type":"string","description":"Presigned URL for uploading the file via PUT. Expires in 1 hour."}},"required":["id","name","url","description","mime_type","size_bytes","status","access_level","created_by","created_at","updated_at","upload_url"]},"CreateProjectResourceRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"Display name for the file."},"mime_type":{"type":"string","description":"MIME type of the file."},"size_bytes":{"type":"number","description":"File size in bytes."},"url":{"type":"string","description":"External URL (e.g. a Google Sheet link). When provided, creates a link-type file with no R2 upload."},"description":{"type":"string","description":"Description for the file."}},"required":["name"]},"ResourceUploadContentResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the upload was stored."}},"required":["success"],"description":"Response payload."}},"required":["data"]},"ProjectResourceResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"resource":{"$ref":"#/components/schemas/Resource"}},"required":["resource"],"description":"Response payload."}},"required":["data"]},"UpdateProjectResourceRequest":{"type":"object","properties":{"name":{"type":"string","description":"New name for the file."},"description":{"type":"string","description":"Description for the file."},"status":{"type":"string","enum":["uploaded"],"description":"Set to `\"uploaded\"` to confirm a pending upload."}}},"DeleteProjectResourceResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"result":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the deletion was successful."}},"required":["success"]}},"required":["result"],"description":"Response payload."}},"required":["data"]},"AnalyticsTestRunsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"daily":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsDaily"}},"summary":{"$ref":"#/components/schemas/AnalyticsRunsSummary"}},"required":["daily","summary"],"description":"Response payload."}},"required":["data"]},"AnalyticsDaily":{"type":"object","properties":{"date":{"type":"string","description":"Date in YYYY-MM-DD format."},"run_count":{"type":"number","description":"Total number of test runs on this date."},"passed_runs":{"type":"number","description":"Number of passed test runs."},"failed_runs":{"type":"number","description":"Number of failed test runs."},"error_runs":{"type":"number","description":"Number of test runs that errored."},"cancelled_runs":{"type":"number","description":"Number of cancelled test runs."},"rerun_count":{"type":"number","description":"Number of reruns on this date."},"total_tests":{"type":"number","description":"Total number of tests executed."},"passed_tests":{"type":"number","description":"Number of passed tests."},"failed_tests":{"type":"number","description":"Number of failed tests."},"failed_tests_after_snooze":{"type":"number","description":"Number of failed tests after applying snoozes."},"flaky_tests":{"type":"number","description":"Number of flaky tests."}},"required":["date","run_count","passed_runs","failed_runs","error_runs","cancelled_runs","rerun_count","total_tests","passed_tests","failed_tests","failed_tests_after_snooze","flaky_tests"]},"AnalyticsRunsSummary":{"type":"object","properties":{"total_runs":{"type":"number","description":"Total number of test runs in the period."},"passed_runs":{"type":"number","description":"Number of passed test runs."},"failed_runs":{"type":"number","description":"Number of failed test runs."},"error_runs":{"type":"number","description":"Number of test runs that errored."},"pass_rate":{"type":"number","description":"Pass rate as a percentage (0-100)."},"avg_tests_per_run":{"type":"number","description":"Average number of tests per run."},"rerun_count":{"type":"number","description":"Total number of reruns."},"rerun_rate":{"type":"number","description":"Rerun rate as a percentage (0-100)."}},"required":["total_runs","passed_runs","failed_runs","error_runs","pass_rate","avg_tests_per_run","rerun_count","rerun_rate"]},"AnalyticsTestCountResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"daily":{"type":"array","items":{"$ref":"#/components/schemas/TestCountByDayEntry"}},"summary":{"$ref":"#/components/schemas/TestCountSummary"}},"required":["daily","summary"],"description":"Response payload."}},"required":["data"]},"TestCountByDayEntry":{"type":"object","properties":{"date":{"type":"string","description":"Date in YYYY-MM-DD format."},"test_count":{"type":"number","description":"Number of distinct test cases that ran on this date (skipped tests excluded)."}},"required":["date","test_count"]},"TestCountSummary":{"type":"object","properties":{"latest_count":{"type":"number","description":"Test count on the most recent day in the window."},"first_count":{"type":"number","description":"Test count on the first day in the window."},"change":{"type":"number","description":"Absolute change between `first_count` and `latest_count`."},"change_percent":{"type":"number","description":"Percentage change (0-100) between first and latest day."}},"required":["latest_count","first_count","change","change_percent"]},"AnalyticsErrorResponse":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message."}},"required":["error"]},"TestCaseHistoryEntry":{"type":"object","properties":{"timestamp":{"type":"string","description":"ISO 8601 timestamp of the history entry."},"status":{"type":"string","description":"Test status for this entry: `pass`, `fail`, `flaky`, or `skip`."},"test_run_id":{"type":"string","description":"ID of the test run this entry belongs to."},"environment_id":{"type":"string","description":"ID of the environment the test ran in."},"executed_at":{"type":"string","description":"ISO 8601 timestamp of when the test executed."},"duration_total":{"type":"number","description":"Total duration in milliseconds."},"retries":{"type":"number","description":"Number of retries for this test execution."}},"required":["timestamp","status","test_run_id","environment_id","executed_at","duration_total","retries"]},"AnalyticsTestCasesSummary":{"type":"object","properties":{"total_test_cases":{"type":"number","description":"Total number of test cases."},"test_cases_with_history":{"type":"number","description":"Number of test cases that have execution history."}},"required":["total_test_cases","test_cases_with_history"]},"AnalyticsTestCasesV2Response":{"type":"object","properties":{"data":{"type":"object","properties":{"test_cases":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsTestCaseV2"}},"summary":{"$ref":"#/components/schemas/AnalyticsTestCasesSummary"}},"required":["test_cases","summary"],"description":"Response payload."}},"required":["data"]},"AnalyticsTestCaseV2":{"type":"object","properties":{"test_case_id":{"type":"string","description":"Unique identifier for the test case."},"test_name":{"type":"string","description":"Name of the test case."},"file_path":{"type":"string","description":"File path of the test case."},"project_name":{"type":"string","description":"Name of the project the test belongs to."},"tags":{"type":"array","items":{"type":"string"},"description":"Array of tags associated with the test case."},"last_run_at":{"type":"string","description":"ISO 8601 timestamp of the last time this test was run."},"metrics":{"type":"object","properties":{"total_runs":{"type":"number","description":"Total number of runs for this test case."},"pass_count":{"type":"number","description":"Number of times the test passed."},"fail_count":{"type":"number","description":"Number of times the test failed."},"flaky_count":{"type":"number","description":"Number of times the test was flaky."},"pass_rate":{"type":"number","description":"Pass rate as a percentage (0-100)."},"fail_rate":{"type":"number","description":"Fail rate as a percentage (0-100)."},"flaky_rate":{"type":"number","description":"Flaky rate as a percentage (0-100)."}},"required":["total_runs","pass_count","fail_count","flaky_count","pass_rate","fail_rate","flaky_rate"],"description":"Aggregated metrics for this test case."}},"required":["test_case_id","test_name","file_path","project_name","tags","last_run_at","metrics"]},"TestCaseHistoryBatchResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"history_by_test_case_id":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/TestCaseHistoryEntry"}},"description":"Map of `test_case_id` to its history entries (most recent first)."}},"required":["history_by_test_case_id"],"description":"Response payload."},"meta":{"type":"object","properties":{"project_id":{"type":"string","description":"Project ID the history was scoped to."},"days":{"type":"integer","nullable":true,"description":"Resolved look-back window in days, or null."},"start_date":{"type":"string","nullable":true,"description":"Resolved custom range start, or null."},"end_date":{"type":"string","nullable":true,"description":"Resolved custom range end, or null."},"environment_id":{"type":"string","nullable":true,"description":"Resolved environment filter, or null."},"limit_per_test_case":{"type":"integer","description":"Resolved per-test-case history limit."},"truncated_test_case_ids":{"type":"array","items":{"type":"string"},"description":"Test case IDs whose history hit the limit and may be truncated."}},"required":["project_id","days","start_date","end_date","environment_id","limit_per_test_case","truncated_test_case_ids"],"description":"Echoed query parameters and truncation metadata."}},"required":["data","meta"]},"TestCaseHistoryUnknownFieldsError":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message."},"unknown_fields":{"type":"array","items":{"type":"string"},"description":"Field names that are not allowed."},"allowed_fields":{"type":"array","items":{"type":"string"},"description":"Field names that are allowed."}},"required":["error","unknown_fields","allowed_fields"]},"TestCaseHistoryBatchRequest":{"type":"object","properties":{"test_case_ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Test case IDs to fetch history for (non-empty)."},"project_id":{"type":"integer","minimum":1,"description":"Project ID (ignored; the project comes from auth scope)."},"days":{"type":"integer","minimum":1,"maximum":90,"description":"Number of days of history (1-90, default 30). Ignored when `start_date` and `end_date` are set."},"start_date":{"type":"string","description":"Start of a custom date range (ISO 8601)."},"end_date":{"type":"string","description":"End of a custom date range (ISO 8601)."},"environment_id":{"type":"string","description":"Filter by environment ID."},"limit_per_test_case":{"type":"integer","minimum":1,"maximum":1000,"description":"Maximum history entries returned per test case (1-1000, default 50)."}},"required":["test_case_ids"],"additionalProperties":{"nullable":true}},"LastPassPerEnvResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LastPassPerEnvEntry"},"description":"Response payload."}},"required":["data"]},"LastPassPerEnvEntry":{"type":"object","properties":{"environment_id":{"type":"string","description":"Environment ID the test last passed in."},"test_run_id":{"type":"string","description":"ID of the test run in which it last passed."},"executed_at":{"type":"number","description":"Epoch timestamp (ms) of the last passing run."}},"required":["environment_id","test_run_id","executed_at"]},"LastPassPerEnvRequest":{"type":"object","properties":{"test_case_id":{"type":"string","minLength":1,"description":"Test case ID to look up."},"project_id":{"type":"integer","minimum":1,"description":"Project ID (ignored; the project comes from auth scope)."},"days":{"type":"integer","minimum":1,"description":"Look-back window in days (default 30)."},"before":{"type":"string","description":"Anchor the lookup to this point in time (ISO 8601); returns the last pass as of then."}},"required":["test_case_id"],"additionalProperties":false},"TestCaseTagsResponse":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"},"description":"Distinct tags across the project's test cases."}},"required":["tags"]},"TestCaseListResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"test_cases":{"type":"array","items":{"$ref":"#/components/schemas/TestCase"},"description":"The page of test cases."},"sync_error":{"type":"object","nullable":true,"properties":{"message":{"type":"string","description":"The error reported by the last test-cases sync."},"occurred_at":{"type":"string","nullable":true,"description":"ISO 8601 timestamp of the sync error, or null."}},"required":["message","occurred_at"],"description":"Details of the project's last test-cases sync error, or null when the last sync succeeded."}},"required":["test_cases","sync_error"],"description":"Test cases plus the project's sync status."},"pagination":{"$ref":"#/components/schemas/TestCaseListPagination"}},"required":["data","pagination"]},"TestCase":{"type":"object","properties":{"id":{"type":"string","description":"Playwright test ID (pw_test_id), a 41-character hash that uniquely identifies the test."},"name":{"type":"string","description":"Test case name."},"file_path":{"type":"string","description":"File path of the test case."},"suites":{"type":"array","items":{"type":"string"},"description":"Array of suite names the test belongs to."},"playwright_project":{"type":"string","description":"Playwright project name."},"tags":{"type":"array","items":{"type":"string"},"description":"Array of tags assigned to the test case."},"created_at":{"type":"string","description":"ISO 8601 timestamp."},"updated_at":{"type":"string","description":"ISO 8601 timestamp."},"first_seen_commit_sha":{"type":"string","nullable":true,"description":"Commit SHA where this test case was first seen. Null if not available."},"last_seen_commit_sha":{"type":"string","nullable":true,"description":"Commit SHA where this test case was last seen. Null if not available."},"dependencies":{"$ref":"#/components/schemas/TestCaseV2Dependencies"}},"required":["id","name","file_path","suites","playwright_project","tags","created_at","updated_at","first_seen_commit_sha","last_seen_commit_sha","dependencies"]},"TestCaseV2Dependencies":{"type":"object","properties":{"playwright_serial":{"type":"object","properties":{"group_id":{"type":"string","description":"ID of the serial dependency group."},"title_path":{"type":"array","items":{"type":"string"},"description":"Full title path of the test within the group."},"file_path":{"type":"string","description":"File path of the test within the group."},"playwright_project":{"type":"string","description":"Playwright project of the group."},"dependency_order":{"type":"integer","description":"Position of this test in the serial order."},"group_size":{"type":"integer","description":"Number of tests in the serial group."},"depends_on":{"type":"array","items":{"type":"string"},"description":"Test IDs this test depends on running first."}},"required":["group_id","title_path","file_path","playwright_project","dependency_order","group_size","depends_on"],"description":"Serial-execution dependency group this test belongs to, if any."}},"description":"Dependency-group metadata for a test case (e.g. Playwright serial groups). Empty object when the test has no dependencies."},"TestCaseListPagination":{"type":"object","properties":{"page":{"type":"integer","description":"Current page (1-indexed)."},"per_page":{"type":"integer","description":"Items per page."},"total":{"type":"integer","description":"Total number of items."},"total_pages":{"type":"integer","description":"Total number of pages."},"has_more":{"type":"boolean","description":"Whether more pages exist beyond the current one."}},"required":["page","per_page","total","total_pages","has_more"],"description":"Pagination metadata."},"GetTestCaseResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"test_case":{"$ref":"#/components/schemas/TestCase"}},"required":["test_case"],"description":"Response payload."}},"required":["data"]},"TestCaseTagsUpdateResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"test_case":{"$ref":"#/components/schemas/TestCase"},"source_file_updated":{"type":"boolean","description":"Whether the source file was updated with the new tags."}},"required":["test_case","source_file_updated"],"description":"The updated test case."},"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message describing why the update partially failed."}},"required":["message"],"description":"Present alongside a non-null `data` only when the update partially failed (e.g. the source file could not be updated)."}},"required":["data"]},"UpdateTestCaseTagsRequest":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"},"description":"Array of tags to assign to the test case."}},"required":["tags"]},"TestRunsListResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"number","description":"Project ID."},"name":{"type":"string","description":"Project name."},"slug":{"type":"string","description":"Project slug."},"repo_name":{"type":"string","description":"Repository name for the project."}},"required":["id","name","slug","repo_name"],"description":"The project the runs belong to."},"test_runs":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TestRunItem"},"description":"The page of test runs."}},"required":["items"],"description":"Paginated test runs."}},"required":["project","test_runs"]},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"TestRunItem":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier."},"state":{"type":"string","enum":["pending","queued","started","ended","error","cancelled","cancelling"],"description":"Current state of the test run."},"total_count":{"type":"number","description":"Total number of test cases."},"success_count":{"type":"number","description":"Number of passed tests."},"failed_count":{"type":"number","description":"Number of failed tests."},"skipped_count":{"type":"number","nullable":true,"description":"Number of skipped tests."},"flaky_count":{"type":"number","nullable":true,"description":"Number of flaky tests."},"duration":{"type":"number","nullable":true,"description":"Duration in milliseconds."},"test_run_branch":{"type":"string","nullable":true,"description":"Git branch being tested."},"environment_slug":{"type":"string","nullable":true,"description":"Environment identifier."},"environment_name":{"type":"string","nullable":true,"description":"Environment display name."},"build_url":{"type":"string","nullable":true,"description":"URL of the build being tested."},"build_branch":{"type":"string","nullable":true,"description":"Git branch of the build."},"commit":{"type":"string","nullable":true,"description":"Commit SHA."},"created_at":{"type":"string","description":"ISO 8601 timestamp."}},"required":["id","state","total_count","success_count","failed_count","skipped_count","flaky_count","duration","test_run_branch","environment_slug","environment_name","build_url","build_branch","commit","created_at"]},"CreateTestRunResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"trigger":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the test run was triggered successfully."}},"required":["success"]},"test_run":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the test run."},"state":{"type":"string","description":"Current state of the test run."},"test_run_branch":{"type":"string","description":"Git branch being tested."},"created_at":{"type":"string","description":"ISO 8601 timestamp when the run was created."}},"required":["id","state","test_run_branch","created_at"]}},"required":["trigger","test_run"]}},"required":["data"]},"TriggerTestRunRequest":{"type":"object","properties":{"environment":{"type":"string","description":"The environment slug to run tests against (e.g., `production`, `staging`)."},"build":{"type":"object","properties":{"url":{"type":"string","description":"URL of the deployed build being tested. Passed to tests as the `BUILD_URL` environment variable, unless `BUILD_URL` is set in `environment_variables_overrides`."},"branch":{"type":"string","description":"Git branch name of the build."},"commit":{"type":"string","description":"Git commit SHA of the build."},"commit_url":{"type":"string","description":"URL to the commit (e.g., GitHub commit link)."},"version":{"type":"string","description":"Version string of the build."}},"description":"Build information to associate with the test run."},"metadata":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Custom key-value pairs to associate with the test run. Values must be strings or numbers."},"environment_variables_overrides":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the environment variable."},"value":{"type":"string","description":"Value of the environment variable."}},"required":["name","value"]},"description":"Environment variables to override for this test run."},"tags":{"type":"array","items":{"type":"string"},"description":"Tags to filter which tests to run (e.g., `@smoke`, `@critical`). Only tests matching these tags will be executed."},"branch":{"type":"string","description":"Test repo branch to run. Useful for running tests from a feature branch before merging. Cannot be used together with `build` — pick one."},"project_id":{"type":"integer","minimum":1,"description":"Legacy project ID; must match the request's resolved project scope when set. The project scope itself comes from the bound principal or the x-project-id header, never from the body."},"send_notifications":{"type":"boolean","description":"Whether to send run notifications. Defaults to true."},"total_shards":{"type":"integer","minimum":1,"description":"Override the number of shards to split the run across."},"rerun_source_id":{"type":"integer","minimum":1,"description":"ID of the test run this run is a rerun of."},"rerun_only_failed":{"type":"boolean","description":"When rerunning, only re-execute the previously failed tests."},"github_actor":{"type":"string","description":"GitHub username that triggered the run, for attribution."}},"required":["environment"]},"GetTestRunResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"test_run":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"number","description":"Project ID."},"name":{"type":"string","description":"Project name."},"repo_name":{"type":"string","description":"Repository name for the project."}},"required":["id","name","repo_name"]},"testRun":{"$ref":"#/components/schemas/TestRunDetail"},"flattenedSummaryDetails":{"type":"array","items":{"nullable":true},"description":"Detailed results for each test case."}},"required":["project","testRun","flattenedSummaryDetails"]}},"required":["test_run"]}},"required":["data"]},"TestRunDetail":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier."},"state":{"type":"string","enum":["pending","queued","started","ended","error","cancelled","cancelling"],"description":"Current state of the test run."},"total_count":{"type":"number","description":"Total number of test cases."},"success_count":{"type":"number","description":"Number of passed tests."},"failed_count":{"type":"number","description":"Number of failed tests."},"skipped_count":{"type":"number","nullable":true,"description":"Number of skipped tests."},"flaky_count":{"type":"number","nullable":true,"description":"Number of flaky tests."},"duration":{"type":"number","nullable":true,"description":"Duration in milliseconds."},"test_run_branch":{"type":"string","nullable":true,"description":"Git branch being tested."},"environment_slug":{"type":"string","nullable":true,"description":"Environment identifier."},"environment_name":{"type":"string","nullable":true,"description":"Environment display name."},"build_url":{"type":"string","nullable":true,"description":"URL of the build being tested."},"build_branch":{"type":"string","nullable":true,"description":"Git branch of the build."},"commit":{"type":"string","nullable":true,"description":"Commit SHA."},"created_at":{"type":"string","description":"ISO 8601 timestamp."},"test_run_head_sha":{"type":"string","nullable":true,"description":"Git commit SHA."},"run_started_at":{"type":"string","nullable":true,"description":"ISO 8601 timestamp when tests started."},"run_ended_at":{"type":"string","nullable":true,"description":"ISO 8601 timestamp when tests ended."},"summary_url":{"type":"string","nullable":true,"description":"URL to download the Playwright JSON report for this run."}},"required":["id","state","total_count","success_count","failed_count","skipped_count","flaky_count","duration","test_run_branch","environment_slug","environment_name","build_url","build_branch","commit","created_at","test_run_head_sha","run_started_at","run_ended_at","summary_url"]},"ListArtifactsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/Artifact"}}},"required":["artifacts"]}},"required":["data"]},"Artifact":{"type":"object","properties":{"key":{"type":"string","description":"Artifact path relative to the test run."},"url":{"type":"string","description":"HTTP URL to download the artifact."},"size":{"type":"number","description":"File size in bytes."},"last_modified":{"type":"string","description":"ISO 8601 timestamp of when the artifact was last modified."}},"required":["key","url","size","last_modified"]},"CancelTestRunResponse":{"type":"object","properties":{"message":{"type":"string","description":"Confirmation message indicating the test run was cancelled."}},"required":["message"]},"CancelTestRunFailureResponse":{"type":"object","properties":{"message":{"type":"string","description":"Reason the test run could not be cancelled."}},"required":["message"]},"CancelTestRunRequest":{"type":"object","properties":{"reason":{"type":"string","description":"Optional reason for cancelling the test run. Recorded for auditing purposes."}}},"RerunTestRunRequest":{"type":"object","properties":{"only_failed":{"type":"boolean","description":"When true, re-run only the failed tests from the source run. The source run must be completed."},"send_notifications":{"type":"boolean","description":"Override whether notifications are sent for the re-run. Defaults to the source run's notification setting."}}},"SkipTestRunResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"check_run_id":{"type":"number","nullable":true,"description":"ID of the GitHub check run created as skipped, or null if no GitHub reporter app is installed for the repo."},"status":{"type":"string","description":"The status of the test run."}},"required":["check_run_id","status"]},"error":{"nullable":true,"description":"Always null for success responses."}},"required":["data","error"]},"SkipTestRunRequest":{"type":"object","properties":{"repo":{"type":"string","description":"Full repository name in owner/repo format."},"commit_sha":{"type":"string","description":"Full Git commit SHA."},"environment":{"type":"string","description":"Environment slug."}},"required":["repo","commit_sha"]}},"parameters":{}},"paths":{"/api/test-runs":{"get":{"tags":["Test Runs"],"operationId":"listTestRuns","summary":"List test runs","description":"Returns a paginated list of test runs for the scoped project, with optional filtering by id, branch, state, result, environment, and time window.","x-mint":{"content":"Test runs transition through these states:\n\n| State | Description |\n|-------|-------------|\n| `pending` | Run is queued behind other runs (concurrency limit reached) |\n| `queued` | Run is queued and will start soon |\n| `started` | Run is currently executing |\n| `ended` | Run completed successfully |\n| `error` | Run failed with an error |\n| `cancelling` | Run is being cancelled |\n| `cancelled` | Run was cancelled |\n\nA failed run may carry one of these error codes:\n\n| Error code | Description |\n|------------|-------------|\n| `merge_conflict` | Merge conflict detected when merging the test branch |\n| `dispatch_failed` | Failed to dispatch the test run to workers |\n| `report_not_generated` | Test report was not generated |\n| `run_failed_with_error` | Test run failed with an error during execution |\n| `worker_interrupted` | Test run worker was interrupted |\n| `merge_reports_failed` | Failed to merge sharded reports |"},"parameters":[{"schema":{"type":"integer","minimum":1,"description":"Return only the test run with this exact ID."},"required":false,"description":"Return only the test run with this exact ID.","name":"id","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Items per page (max 100). Defaults to 20."},"required":false,"description":"Items per page (max 100). Defaults to 20.","name":"per_page","in":"query"},{"schema":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-indexed). Defaults to 1."},"required":false,"description":"Page number (1-indexed). Defaults to 1.","name":"page","in":"query"},{"schema":{"type":"string","description":"Filter to runs on this test repo branch."},"required":false,"description":"Filter to runs on this test repo branch.","name":"branch","in":"query"},{"schema":{"type":"integer","minimum":1,"description":"Restrict to runs created in the last N days."},"required":false,"description":"Restrict to runs created in the last N days.","name":"interval_in_days","in":"query"},{"schema":{"type":"string","description":"Filter by run state (e.g. `queued`, `ended`)."},"required":false,"description":"Filter by run state (e.g. `queued`, `ended`).","name":"state","in":"query"},{"schema":{"type":"string","enum":["passed","failed"],"description":"Filter by overall run result."},"required":false,"description":"Filter by overall run result.","name":"result","in":"query"},{"schema":{"type":"string","description":"Comma-separated environment IDs to filter by."},"required":false,"description":"Comma-separated environment IDs to filter by.","name":"environment_ids","in":"query"}],"responses":{"200":{"description":"Paginated list of test runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRunsListResponse"}}}},"400":{"description":"Invalid request, or the project could not be resolved from the request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"tags":["Test Runs"],"operationId":"createTestRun","summary":"Trigger a test run","description":"Triggers a new end-to-end test run for the scoped project against the given environment, resolving the branch/build and dispatching the run.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerTestRunRequest"}}}},"responses":{"200":{"description":"The triggered test run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestRunResponse"}}}},"400":{"description":"Invalid request, unknown environment, conflicting branch/build, pierre code-storage repo, or the project could not be resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/test-runs/{id}":{"get":{"tags":["Test Runs"],"operationId":"getTestRun","summary":"Get a test run","description":"Retrieves a single test run by ID, including its project and per-test summary details. Access is derived from the run's project; inaccessible or unknown runs return 404.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Test run ID."},"required":true,"description":"Test run ID.","name":"id","in":"path"}],"responses":{"200":{"description":"The test run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTestRunResponse"}}}},"400":{"description":"Invalid x-project-id or x-project-slug header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Test run not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/test-runs/{id}/artifacts":{"get":{"tags":["Test Runs"],"operationId":"listTestRunArtifacts","summary":"List test run artifacts","description":"Lists downloadable artifacts (traces, reports, screenshots, etc.) stored for a test run. Provide `key` for an exact lookup, or `prefix`/`suffix` to filter the listing. Returns an empty list when nothing matches.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Test run database ID."},"required":true,"description":"Test run database ID.","name":"id","in":"path"},{"schema":{"type":"string","description":"Exact artifact path (relative to the run) to look up via a single HEAD request. Takes precedence over `prefix`/`suffix`."},"required":false,"description":"Exact artifact path (relative to the run) to look up via a single HEAD request. Takes precedence over `prefix`/`suffix`.","name":"key","in":"query"},{"schema":{"type":"string","description":"Restrict the listing to artifacts whose path starts with this."},"required":false,"description":"Restrict the listing to artifacts whose path starts with this.","name":"prefix","in":"query"},{"schema":{"type":"string","description":"Restrict the listing to artifacts whose path ends with this."},"required":false,"description":"Restrict the listing to artifacts whose path ends with this.","name":"suffix","in":"query"}],"responses":{"200":{"description":"The matching artifacts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListArtifactsResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Test run not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/test-runs/{id}/cancel":{"post":{"tags":["Test Runs"],"operationId":"cancelTestRun","summary":"Cancel a test run","description":"Cancels an in-progress test run, recording an optional reason and the cancelling user. Returns 412 when the run is already in a terminal state.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Database ID of the test run."},"required":true,"description":"Database ID of the test run.","name":"id","in":"path"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelTestRunRequest"}}}},"responses":{"200":{"description":"The test run was cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelTestRunResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Test run not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"412":{"description":"The test run could not be cancelled (already terminal).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelTestRunFailureResponse"}}}}}}},"/api/test-runs/{id}/re-run":{"post":{"tags":["Test Runs"],"operationId":"rerunTestRun","summary":"Re-run a test run","description":"Re-triggers a test run from a source run's branch, build, and overrides. Optionally re-runs only the failed tests of a completed run.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Database ID of the test run."},"required":true,"description":"Database ID of the test run.","name":"id","in":"path"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RerunTestRunRequest"}}}},"responses":{"200":{"description":"The re-triggered test run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestRunResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Source test run or environment not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/test-runs/skip":{"post":{"tags":["Test Runs"],"operationId":"skipTestRun","summary":"Skip a test run","description":"Marks the Empirical check run on a commit as skipped. Callers authenticate with a project API key.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkipTestRunRequest"}}}},"responses":{"200":{"description":"The skipped check run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkipTestRunResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/test-cases/tags":{"get":{"tags":["Test Cases"],"operationId":"listTestCaseTags","summary":"List test case tags","description":"Returns the distinct set of tags across the scoped project's test cases. The response is a bare `{ tags }` object (no `data` envelope) and is cached for 5 minutes via a `Cache-Control` header.","responses":{"200":{"description":"The project's distinct test case tags.","headers":{"Cache-Control":{"schema":{"type":"string","description":"Caching directive: `public, s-maxage=300, stale-while-revalidate=600`."},"required":true,"description":"Caching directive: `public, s-maxage=300, stale-while-revalidate=600`."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestCaseTagsResponse"}}}},"400":{"description":"Project could not be resolved from the request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/test-cases":{"get":{"tags":["Test Cases"],"operationId":"listTestCases","summary":"List test cases","description":"Returns a paginated list of the scoped project's test cases with optional filtering, along with the project's latest test-cases sync error.","parameters":[{"schema":{"type":"string","description":"Comma-separated Playwright test IDs to filter by. Capped at the first 100."},"required":false,"description":"Comma-separated Playwright test IDs to filter by. Capped at the first 100.","name":"ids","in":"query"},{"schema":{"type":"string","description":"Filter to test cases whose file path starts with this value."},"required":false,"description":"Filter to test cases whose file path starts with this value.","name":"file_path","in":"query"},{"schema":{"type":"string","description":"Filter to test cases in this Playwright project."},"required":false,"description":"Filter to test cases in this Playwright project.","name":"playwright_project","in":"query"},{"schema":{"type":"string","description":"Comma-separated tags; returns test cases having any of the given tags."},"required":false,"description":"Comma-separated tags; returns test cases having any of the given tags.","name":"tags","in":"query"},{"schema":{"type":"string","description":"Free-text search across the test case name and file path."},"required":false,"description":"Free-text search across the test case name and file path.","name":"search","in":"query"},{"schema":{"type":"string","description":"Filter to test cases first seen at this commit SHA."},"required":false,"description":"Filter to test cases first seen at this commit SHA.","name":"first_seen_commit_sha","in":"query"},{"schema":{"type":"string","description":"Filter to test cases last seen at this commit SHA."},"required":false,"description":"Filter to test cases last seen at this commit SHA.","name":"last_seen_commit_sha","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"When `true` (default), restrict to test cases present at the project's last synced commit. Set `false` to include all test cases."},"required":false,"description":"When `true` (default), restrict to test cases present at the project's last synced commit. Set `false` to include all test cases.","name":"is_active","in":"query"},{"schema":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-indexed). Defaults to 1."},"required":false,"description":"Page number (1-indexed). Defaults to 1.","name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"default":100,"description":"Items per page. Defaults to 100."},"required":false,"description":"Items per page. Defaults to 100.","name":"per_page","in":"query"}],"responses":{"200":{"description":"Paginated test cases and sync status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestCaseListResponse"}}}},"400":{"description":"Invalid request or project could not be resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/test-cases/{id}":{"get":{"tags":["Test Cases"],"operationId":"getTestCase","summary":"Get a test case","description":"Retrieves a single test case by its Playwright test ID. Access is derived from the test case's project, returning 403 when the caller cannot access it.","parameters":[{"schema":{"type":"string","minLength":1,"description":"Playwright test ID (pw_test_id), the 41-character hash identifying the test case."},"required":true,"description":"Playwright test ID (pw_test_id), the 41-character hash identifying the test case.","name":"id","in":"path"}],"responses":{"200":{"description":"The test case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTestCaseResponse"}}}},"403":{"description":"The test case belongs to a project you cannot access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Test case not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Test Cases"],"operationId":"updateTestCaseTags","summary":"Update test case tags","description":"Replaces the tags on a test case and attempts to update the source file. Access is derived from the test case's project, returning 403 when the caller cannot access it.","parameters":[{"schema":{"type":"string","minLength":1,"description":"Playwright test ID (pw_test_id), the 41-character hash identifying the test case."},"required":true,"description":"Playwright test ID (pw_test_id), the 41-character hash identifying the test case.","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTestCaseTagsRequest"}}}},"responses":{"200":{"description":"The updated test case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestCaseTagsUpdateResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The test case belongs to a project you cannot access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Test case not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/environments/list":{"get":{"tags":["Environments"],"operationId":"listEnvironments","summary":"List environments","description":"Returns the scoped project's environments, optionally filtered by slug and disabled state. The run worker uses this to resolve env vars by slug.","parameters":[{"schema":{"type":"string","description":"When set to the string `false`, exclude disabled environments."},"required":false,"description":"When set to the string `false`, exclude disabled environments.","name":"is_disabled","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Filter to the environment with this slug."},"required":false,"description":"Filter to the environment with this slug.","name":"environment_slug","in":"query"}],"responses":{"200":{"description":"The project's environments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvironmentsResponse"}}}},"400":{"description":"Invalid request or missing project scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/environments":{"post":{"tags":["Environments"],"operationId":"createEnvironment","summary":"Create an environment","description":"Creates an environment in the scoped project. Rejected for projects managed by environments.yaml.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEnvironmentRequest"}}}},"responses":{"200":{"description":"The created environment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentResponse"}}}},"400":{"description":"Invalid request, or the slug already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Project is managed by environments.yaml.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/environments/sync":{"post":{"tags":["Environments"],"operationId":"syncEnvironments","summary":"Sync environments from YAML","description":"Upserts the scoped project's environments from its environments.yaml file and flags the project as YAML-managed.","responses":{"200":{"description":"Sync succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentSyncResponse"}}}},"400":{"description":"Invalid environments.yaml.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Project not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/environments/{id}":{"patch":{"tags":["Environments"],"operationId":"updateEnvironment","summary":"Update an environment","description":"Updates an environment by ID. The project is derived from the entity. Returns the bare updated row (no `data` envelope), matching the dashboard settings UI it serves.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Environment ID."},"required":true,"description":"Environment ID.","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEnvironmentRequest"}}}},"responses":{"200":{"description":"The updated environment row (no `data` envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Environment"}}}},"400":{"description":"Invalid request, or the slug already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentMutationError"}}}},"403":{"description":"Environment is in an environments.yaml-managed project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentMutationError"}}}},"404":{"description":"Environment not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentMutationError"}}}}}},"delete":{"tags":["Environments"],"operationId":"deleteEnvironment","summary":"Delete an environment","description":"Deletes an environment by ID and prunes environment-specific subscriptions. The project is derived from the entity.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Environment ID."},"required":true,"description":"Environment ID.","name":"id","in":"path"}],"responses":{"200":{"description":"Deletion succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentDeleteResponse"}}}},"403":{"description":"Environment is in an environments.yaml-managed project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentMutationError"}}}},"404":{"description":"Environment not found or not accessible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentMutationError"}}}}}}},"/api/snoozes":{"get":{"tags":["Snoozes"],"operationId":"listSnoozes","summary":"List snoozes","description":"Returns a paginated list of snoozes for the scoped project, with optional filtering by environment and status.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-indexed)."},"required":false,"description":"Page number (1-indexed).","name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Items per page (max 100)."},"required":false,"description":"Items per page (max 100).","name":"per_page","in":"query"},{"schema":{"type":"integer","minimum":1,"description":"Filter to snoozes scoped to this environment."},"required":false,"description":"Filter to snoozes scoped to this environment.","name":"environment_id","in":"query"},{"schema":{"type":"string","enum":["active","expired","all"],"default":"active","description":"Filter by snooze status."},"required":false,"description":"Filter by snooze status.","name":"status","in":"query"}],"responses":{"200":{"description":"Paginated list of snoozes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnoozeListResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Snoozes"],"operationId":"createSnooze","summary":"Create a snooze","description":"Creates a snooze to temporarily suppress failures for the given Playwright tests until `snooze_until`.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSnoozeRequest"}}}},"responses":{"200":{"description":"The created snooze.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnoozeResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/snoozes/{id}":{"get":{"tags":["Snoozes"],"operationId":"getSnooze","summary":"Get a snooze","description":"Retrieves a single snooze by ID.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Snooze ID."},"required":true,"description":"Snooze ID.","name":"id","in":"path"}],"responses":{"200":{"description":"The snooze.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnoozeResponse"}}}},"404":{"description":"Snooze not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Snoozes"],"operationId":"updateSnooze","summary":"Update a snooze","description":"Updates a snooze — extend it, change its tests/description, or expire it immediately with `expire_now`.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Snooze ID."},"required":true,"description":"Snooze ID.","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSnoozeRequest"}}}},"responses":{"200":{"description":"The updated snooze.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnoozeResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Snooze not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Snoozes"],"operationId":"deleteSnooze","summary":"Delete a snooze","description":"Permanently deletes a snooze.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Snooze ID."},"required":true,"description":"Snooze ID.","name":"id","in":"path"}],"responses":{"200":{"description":"The deleted snooze.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnoozeResponse"}}}},"404":{"description":"Snooze not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/snoozes/{id}/add-test-cases":{"post":{"tags":["Snoozes"],"operationId":"addSnoozeTestCases","summary":"Add tests to a snooze","description":"Adds Playwright tests to an existing snooze.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Snooze ID."},"required":true,"description":"Snooze ID.","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSnoozeTestCasesRequest"}}}},"responses":{"200":{"description":"The updated snooze.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnoozeResponse"}}}},"404":{"description":"Snooze not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/analytics/test-runs":{"get":{"tags":["Analytics"],"operationId":"listTestRunsAnalyticsV2","summary":"Test-run analytics","description":"Returns daily test-run statistics and a period summary for the scoped project.","x-mint":{"content":"Analytics uses path-based versioning; the current recommended version is **v2** (`/api/v2/analytics/`). The **v1** endpoints (`/api/v1/analytics/*`) are deprecated but remain available for backward compatibility. In v2, the v1 `test-cases` endpoint is split: `GET /api/v2/analytics/test-cases` returns metrics only, and `POST /api/v2/analytics/test-case-history/batch` fetches history for one or many test cases in a single request.\n\nPercentage fields (`pass_rate`, `fail_rate`, `flaky_rate`) are integers in the **0-100** range, not decimals. All endpoints support date filtering via `days` (1-90), or a custom `start_date`/`end_date` range (ISO 8601)."},"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":90,"description":"Number of days to include (1-90). Ignored when both `start_date` and `end_date` are provided."},"required":false,"description":"Number of days to include (1-90). Ignored when both `start_date` and `end_date` are provided.","name":"days","in":"query"},{"schema":{"type":"string","description":"Start of a custom date range (ISO 8601)."},"required":false,"description":"Start of a custom date range (ISO 8601).","name":"start_date","in":"query"},{"schema":{"type":"string","description":"End of a custom date range (ISO 8601)."},"required":false,"description":"End of a custom date range (ISO 8601).","name":"end_date","in":"query"},{"schema":{"type":"integer","minimum":1,"description":"Filter to a single environment by ID."},"required":false,"description":"Filter to a single environment by ID.","name":"environment_id","in":"query"},{"schema":{"type":"string","description":"Filter to test runs on this git branch."},"required":false,"description":"Filter to test runs on this git branch.","name":"branch","in":"query"}],"responses":{"200":{"description":"Daily test-run stats and a period summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsTestRunsResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/analytics/test-count":{"get":{"tags":["Analytics"],"operationId":"getTestCountAnalyticsV2","summary":"Test-count analytics","description":"Returns the number of distinct test cases run per day for the scoped project.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":90,"description":"Number of days to include (1-90). Ignored when both `start_date` and `end_date` are provided."},"required":false,"description":"Number of days to include (1-90). Ignored when both `start_date` and `end_date` are provided.","name":"days","in":"query"},{"schema":{"type":"string","description":"Start of a custom date range (ISO 8601)."},"required":false,"description":"Start of a custom date range (ISO 8601).","name":"start_date","in":"query"},{"schema":{"type":"string","description":"End of a custom date range (ISO 8601)."},"required":false,"description":"End of a custom date range (ISO 8601).","name":"end_date","in":"query"},{"schema":{"type":"string","description":"Filter to a single environment by ID."},"required":false,"description":"Filter to a single environment by ID.","name":"environment_id","in":"query"}],"responses":{"200":{"description":"Daily distinct test counts and a period summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsTestCountResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Analytics backend error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsErrorResponse"}}}}}}},"/api/v2/analytics/test-cases":{"get":{"tags":["Analytics"],"operationId":"listTestCasesAnalyticsV2","summary":"Test-case analytics","description":"Returns aggregated test-case metrics for the scoped project (without per-test history).","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":90,"description":"Number of days to include (1-90). Ignored when both `start_date` and `end_date` are provided."},"required":false,"description":"Number of days to include (1-90). Ignored when both `start_date` and `end_date` are provided.","name":"days","in":"query"},{"schema":{"type":"string","description":"Start of a custom date range (ISO 8601)."},"required":false,"description":"Start of a custom date range (ISO 8601).","name":"start_date","in":"query"},{"schema":{"type":"string","description":"End of a custom date range (ISO 8601)."},"required":false,"description":"End of a custom date range (ISO 8601).","name":"end_date","in":"query"},{"schema":{"type":"string","description":"Filter to a single environment by ID."},"required":false,"description":"Filter to a single environment by ID.","name":"environment_id","in":"query"},{"schema":{"type":"string","description":"Free-text search over test name and file path."},"required":false,"description":"Free-text search over test name and file path.","name":"search","in":"query"},{"schema":{"type":"string","description":"Comma-separated list of tags to filter by."},"required":false,"description":"Comma-separated list of tags to filter by.","name":"tags","in":"query"},{"schema":{"type":"string","enum":["fail_rate","flaky_rate","last_run"],"description":"Field to order results by. One of `fail_rate`, `flaky_rate`, `last_run`. Defaults to `fail_rate`."},"required":false,"description":"Field to order results by. One of `fail_rate`, `flaky_rate`, `last_run`. Defaults to `fail_rate`.","name":"order_by","in":"query"},{"schema":{"type":"string","enum":["asc","desc"],"description":"Sort direction, `asc` or `desc`. Defaults to `desc`."},"required":false,"description":"Sort direction, `asc` or `desc`. Defaults to `desc`.","name":"order","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"description":"Maximum number of test cases to return (1-500)."},"required":false,"description":"Maximum number of test cases to return (1-500).","name":"limit","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of history entries to return per test case (1-100)."},"required":false,"description":"Maximum number of history entries to return per test case (1-100).","name":"limit_history","in":"query"},{"schema":{"type":"string","description":"Filter to a single test case by ID."},"required":false,"description":"Filter to a single test case by ID.","name":"test_case_id","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"When `false`, include inactive test cases. Defaults to active-only."},"required":false,"description":"When `false`, include inactive test cases. Defaults to active-only.","name":"is_active","in":"query"}],"responses":{"200":{"description":"Test cases with metrics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsTestCasesV2Response"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Analytics backend error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsErrorResponse"}}}}}}},"/api/v2/analytics/test-case-history/batch":{"post":{"tags":["Analytics"],"operationId":"getTestCaseHistoryBatch","summary":"Test-case execution history (batch)","description":"Returns per-test-case execution history for the requested test cases, plus echoed query metadata.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestCaseHistoryBatchRequest"}}}},"responses":{"200":{"description":"History grouped by test case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestCaseHistoryBatchResponse"}}}},"400":{"description":"Unknown body field(s) supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestCaseHistoryUnknownFieldsError"}}}},"500":{"description":"Analytics backend error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsErrorResponse"}}}}}}},"/api/v2/analytics/test-case-last-pass-per-env":{"post":{"tags":["Analytics"],"operationId":"getTestCaseLastPassPerEnv","summary":"Last passing run per environment","description":"Returns the most recent passing run for a test case, grouped by environment.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LastPassPerEnvRequest"}}}},"responses":{"200":{"description":"Last passing run per environment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LastPassPerEnvResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Analytics backend error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsErrorResponse"}}}}}}},"/api/resources":{"get":{"tags":["Resources"],"operationId":"listProjectResources","summary":"List resources","description":"Returns a paginated list of uploaded resources for the project.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-indexed)."},"required":false,"description":"Page number (1-indexed).","name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"default":20,"description":"Items per page (max 100)."},"required":false,"description":"Items per page (max 100).","name":"per_page","in":"query"}],"responses":{"200":{"description":"Paginated list of resources.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectResourceListResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Resources"],"operationId":"createProjectResource","summary":"Create a resource","description":"Creates a resource. With `url`, creates a link-type file; otherwise creates a pending upload and returns a signed `upload_url` to stream the file into R2.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProjectResourceRequest"}}}},"responses":{"200":{"description":"The created resource, including an `upload_url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProjectResourceResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/resources/{id}/content":{"get":{"tags":["Resources"],"operationId":"downloadResourceContent","summary":"Download resource content","description":"Streams an uploaded resource's content from R2. Authorized by the signed capability URL (HMAC in the query string), not bearer auth.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Resource ID."},"required":true,"description":"Resource ID.","name":"id","in":"path"},{"schema":{"type":"string","enum":["r","w"],"description":"Capability scope: `r` (read) or `w` (write)."},"required":true,"description":"Capability scope: `r` (read) or `w` (write).","name":"scope","in":"query"},{"schema":{"type":"integer","nullable":true,"description":"Expiry as a Unix timestamp (seconds)."},"required":false,"description":"Expiry as a Unix timestamp (seconds).","name":"exp","in":"query"},{"schema":{"type":"string","description":"HMAC signature for the capability."},"required":true,"description":"HMAC signature for the capability.","name":"sig","in":"query"}],"responses":{"200":{"description":"The resource's binary content.","content":{"application/octet-stream":{"schema":{"type":"string","description":"Binary file content."}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Invalid scope or expired URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"tags":["Resources"],"operationId":"uploadResourceContent","summary":"Upload resource content","description":"Streams the request body into R2 for a pending resource. Authorized by the signed capability URL (HMAC in the query string), not bearer auth.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Resource ID."},"required":true,"description":"Resource ID.","name":"id","in":"path"},{"schema":{"type":"string","enum":["r","w"],"description":"Capability scope: `r` (read) or `w` (write)."},"required":true,"description":"Capability scope: `r` (read) or `w` (write).","name":"scope","in":"query"},{"schema":{"type":"integer","nullable":true,"description":"Expiry as a Unix timestamp (seconds)."},"required":false,"description":"Expiry as a Unix timestamp (seconds).","name":"exp","in":"query"},{"schema":{"type":"string","description":"HMAC signature for the capability."},"required":true,"description":"HMAC signature for the capability.","name":"sig","in":"query"}],"requestBody":{"required":false,"content":{"application/octet-stream":{"schema":{"type":"string","description":"Raw file bytes to store in R2."}}}},"responses":{"200":{"description":"The upload was stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceUploadContentResponse"}}}},"400":{"description":"Invalid request or missing body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Invalid scope or expired URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/resources/{id}":{"get":{"tags":["Resources"],"operationId":"getProjectResource","summary":"Get a resource","description":"Retrieves a single resource by ID, with a fresh download URL.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Resource ID."},"required":true,"description":"Resource ID.","name":"id","in":"path"}],"responses":{"200":{"description":"The resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectResourceResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Resources"],"operationId":"updateProjectResource","summary":"Update a resource","description":"Updates a resource's name/description, or confirms a pending upload with `status: \"uploaded\"`.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Resource ID."},"required":true,"description":"Resource ID.","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProjectResourceRequest"}}}},"responses":{"200":{"description":"The updated resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectResourceResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Resources"],"operationId":"deleteProjectResource","summary":"Delete a resource","description":"Deletes a resource, removing its R2 object and stopping any active Drive watch.","parameters":[{"schema":{"type":"integer","minimum":1,"description":"Resource ID."},"required":true,"description":"Resource ID.","name":"id","in":"path"}],"responses":{"200":{"description":"The resource was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteProjectResourceResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/badges/projects/{project_slug}/environments/{environment_slug}":{"get":{"tags":["Badges"],"operationId":"getEnvironmentBadge","summary":"Get an environment status badge","description":"Renders an SVG status badge (passing/failing/running/etc.) for the latest test run of a project environment. Served unauthenticated for embedding in READMEs.","parameters":[{"schema":{"type":"string","minLength":1,"description":"Slug of the project that owns the environment."},"required":true,"description":"Slug of the project that owns the environment.","name":"project_slug","in":"path"},{"schema":{"type":"string","minLength":1,"description":"Slug of the environment to render a badge for."},"required":true,"description":"Slug of the environment to render a badge for.","name":"environment_slug","in":"path"},{"schema":{"type":"string","description":"Restrict the badge to the latest run on this branch. Blank/whitespace is treated as unset (latest run across branches)."},"required":false,"description":"Restrict the badge to the latest run on this branch. Blank/whitespace is treated as unset (latest run across branches).","name":"branch","in":"query"},{"schema":{"type":"string","description":"Left-hand label text on the badge. Blank/whitespace falls back to the default (`empirical tests`)."},"required":false,"description":"Left-hand label text on the badge. Blank/whitespace falls back to the default (`empirical tests`).","name":"label","in":"query"}],"responses":{"200":{"description":"The rendered SVG badge image.","content":{"image/svg+xml":{"schema":{"type":"string","description":"SVG markup for the status badge."}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}