{
  "openapi": "3.1.0",
  "info": {
    "title": "qaitai API",
    "version": "1.0.0",
    "summary": "Start and follow AI QA runs from CI and scripts.",
    "description": "Authenticate with a workspace API key: `Authorization: Bearer qak_…` (Settings → API keys).\n\nA key acts for its workspace only and needs the scope named on each operation.\n\nScopes: `runs:read`, `runs:write`, `suites:read`, `suites:write`, `issues:read`, `projects:read`.\n\nErrors are `{ \"error\": { \"code\", \"message\" } }`. Lists are newest first with cursor pagination.\n\nLimits per key and minute depend on the plan (Free 60, Pro 300, Team 600, Enterprise and self-hosted 1200); responses carry X-RateLimit-* headers.",
    "license": {
      "name": "AGPL-3.0-only",
      "identifier": "AGPL-3.0-only"
    }
  },
  "servers": [
    {
      "url": "/api/v1",
      "description": "This instance"
    }
  ],
  "tags": [
    {
      "name": "Runs",
      "description": "Start, follow and cancel runs."
    },
    {
      "name": "Suites",
      "description": "Saved journeys you can run on demand or on a schedule."
    },
    {
      "name": "Issues",
      "description": "Findings reported by workers."
    },
    {
      "name": "Projects",
      "description": "Apps under test."
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/runs": {
      "post": {
        "operationId": "createRun",
        "tags": [
          "Runs"
        ],
        "summary": "Start a run",
        "description": "Same checks as starting a run in the app: the project must be in the key's workspace, cloud runs on qaitai Cloud need a verified domain, and plan concurrency, allowance and spend caps apply. Policy refusals are 422 `run_rejected` with a `reason`.",
        "security": [
          {
            "apiKey": [
              "runs:write"
            ]
          }
        ],
        "x-qaitai-scope": "runs:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Retries with the same key within 24 hours return the first run (200, `Idempotent-Replayed: true`) instead of starting another. Reusing a key for a different request is a 409.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x21-\\x7e]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRunRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay: the earlier run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "201": {
            "description": "The queued run. 200 when an Idempotency-Key replay returns an earlier run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: already finished, or Idempotency-Key reused (`conflict`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Refused by policy: unverified domain, plan limit, … (`run_rejected`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listRuns",
        "tags": [
          "Runs"
        ],
        "summary": "List runs",
        "security": [
          {
            "apiKey": [
              "runs:read"
            ]
          }
        ],
        "x-qaitai-scope": "runs:read",
        "parameters": [
          {
            "name": "projectId",
            "in": "query",
            "description": "Only this project's runs.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "suiteId",
            "in": "query",
            "description": "Only this suite's runs.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only runs with this status.",
            "schema": {
              "$ref": "#/components/schemas/RunStatus"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "`nextCursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, 1-100 (default 20).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/runs/{runId}": {
      "get": {
        "operationId": "getRun",
        "tags": [
          "Runs"
        ],
        "summary": "Get a run",
        "description": "The run with its workers and findings. Poll this or stream /events until `status` is terminal.",
        "security": [
          {
            "apiKey": [
              "runs:read"
            ]
          }
        ],
        "x-qaitai-scope": "runs:read",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "description": "The run id.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/runs/{runId}/cancel": {
      "post": {
        "operationId": "cancelRun",
        "tags": [
          "Runs"
        ],
        "summary": "Cancel a run",
        "security": [
          {
            "apiKey": [
              "runs:write"
            ]
          }
        ],
        "x-qaitai-scope": "runs:write",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "description": "The run id.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: already finished, or Idempotency-Key reused (`conflict`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/runs/{runId}/events": {
      "get": {
        "operationId": "streamRunEvents",
        "tags": [
          "Runs"
        ],
        "summary": "Stream run events (SSE)",
        "description": "Server-sent events until the run finishes: `run` {id, status, startedAt, finishedAt} on change, `worker` {id, label, status, startedAt, finishedAt} on change, `step` {id, runWorkerId, index, kind, summary, at} (the SSE `id` is its timestamp), `issue` {id, severity, title, stepId, createdAt}, then `end` {}. The server closes the stream after 10 minutes; reconnect with `Last-Event-ID` to resume without repeating steps.",
        "security": [
          {
            "apiKey": [
              "runs:read"
            ]
          }
        ],
        "x-qaitai-scope": "runs:read",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "description": "The run id.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "Last-Event-ID",
            "in": "header",
            "description": "Resume after this step.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "An event stream.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "example": "event: run\ndata: {\"id\":\"…\",\"status\":\"running\",\"startedAt\":\"…\",\"finishedAt\":null}\n\n"
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/suites": {
      "get": {
        "operationId": "listSuites",
        "tags": [
          "Suites"
        ],
        "summary": "List suites",
        "security": [
          {
            "apiKey": [
              "suites:read"
            ]
          }
        ],
        "x-qaitai-scope": "suites:read",
        "parameters": [
          {
            "name": "projectId",
            "in": "query",
            "description": "Only this project's suites.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "`nextCursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, 1-100 (default 20).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuitePage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createSuite",
        "tags": [
          "Suites"
        ],
        "summary": "Create a suite",
        "security": [
          {
            "apiKey": [
              "suites:write"
            ]
          }
        ],
        "x-qaitai-scope": "suites:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSuiteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay: the earlier run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Suite"
                }
              }
            }
          },
          "201": {
            "description": "The suite.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Suite"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/suites/{suiteId}": {
      "get": {
        "operationId": "getSuite",
        "tags": [
          "Suites"
        ],
        "summary": "Get a suite",
        "security": [
          {
            "apiKey": [
              "suites:read"
            ]
          }
        ],
        "x-qaitai-scope": "suites:read",
        "parameters": [
          {
            "name": "suiteId",
            "in": "path",
            "required": true,
            "description": "The suite id.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The suite.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Suite"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/suites/{suiteId}/run": {
      "post": {
        "operationId": "runSuite",
        "tags": [
          "Suites"
        ],
        "summary": "Run a suite",
        "description": "Starts the suite now, against `url` or the project's app URL: a replay when the suite is compiled, else an exploratory run. Needs `runs:write` because it starts a run.",
        "security": [
          {
            "apiKey": [
              "runs:write"
            ]
          }
        ],
        "x-qaitai-scope": "runs:write",
        "parameters": [
          {
            "name": "suiteId",
            "in": "path",
            "required": true,
            "description": "The suite id.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Retries with the same key within 24 hours return the first run (200, `Idempotent-Replayed: true`) instead of starting another. Reusing a key for a different request is a 409.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x21-\\x7e]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunSuiteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay: the earlier run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "201": {
            "description": "The queued run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: already finished, or Idempotency-Key reused (`conflict`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Refused by policy: unverified domain, plan limit, … (`run_rejected`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/issues": {
      "get": {
        "operationId": "listIssues",
        "tags": [
          "Issues"
        ],
        "summary": "List issues",
        "security": [
          {
            "apiKey": [
              "issues:read"
            ]
          }
        ],
        "x-qaitai-scope": "issues:read",
        "parameters": [
          {
            "name": "runId",
            "in": "query",
            "description": "Only findings of this run.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "in": "query",
            "description": "Only this project's findings.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "severity",
            "in": "query",
            "description": "Only this severity.",
            "schema": {
              "$ref": "#/components/schemas/Severity"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "e.g. \"open\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "`nextCursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, 1-100 (default 20).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssuePage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/issues/{issueId}": {
      "get": {
        "operationId": "getIssue",
        "tags": [
          "Issues"
        ],
        "summary": "Get an issue",
        "security": [
          {
            "apiKey": [
              "issues:read"
            ]
          }
        ],
        "x-qaitai-scope": "issues:read",
        "parameters": [
          {
            "name": "issueId",
            "in": "path",
            "required": true,
            "description": "The issue id.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The issue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Issue"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in the key's workspace (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "security": [
          {
            "apiKey": [
              "projects:read"
            ]
          }
        ],
        "x-qaitai-scope": "projects:read",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "description": "`nextCursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, 1-100 (default 20).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key (`unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limited`); see Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "qak_…",
        "description": "Workspace API key."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "$ref": "#/components/schemas/ErrorCode"
              },
              "message": {
                "type": "string",
                "description": "Safe to show to a person."
              },
              "reason": {
                "type": "string",
                "description": "For run_rejected: domain_unverified, concurrency_limit, plan_limit, private_host, byok_required, no_url, queue_unavailable or rejected."
              },
              "issues": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "For invalid_request: `path: problem` lines."
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "ErrorCode": {
        "type": "string",
        "enum": [
          "invalid_request",
          "unauthorized",
          "forbidden",
          "not_found",
          "conflict",
          "run_rejected",
          "payload_too_large",
          "rate_limited",
          "internal_error",
          "unavailable"
        ]
      },
      "Run": {
        "type": "object",
        "required": [
          "id",
          "projectId",
          "suiteId",
          "kind",
          "status",
          "prompt",
          "targetUrl",
          "gitRef",
          "workerCount",
          "maxSteps",
          "maxMinutes",
          "runtime",
          "keyMode",
          "model",
          "createdBy",
          "rerunOf",
          "createdAt",
          "startedAt",
          "finishedAt",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "suiteId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "agent",
              "replay"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "prompt": {
            "type": "string"
          },
          "targetUrl": {
            "type": "string"
          },
          "gitRef": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "workerCount": {
            "type": "number"
          },
          "maxSteps": {
            "type": "number",
            "description": "Step limit per worker."
          },
          "maxMinutes": {
            "type": "number",
            "description": "Time limit per worker."
          },
          "runtime": {
            "type": "string",
            "enum": [
              "cloud",
              "self_hosted"
            ]
          },
          "keyMode": {
            "type": "string",
            "enum": [
              "managed",
              "byok"
            ]
          },
          "model": {
            "type": "string"
          },
          "createdBy": {
            "type": "string",
            "description": "Member id, \"github:<login>\", \"schedule:<trigger>\" or \"api_key:<id>\"."
          },
          "rerunOf": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "finishedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "url": {
            "type": "string",
            "description": "The run in the qaitai app."
          }
        },
        "additionalProperties": false
      },
      "RunStatus": {
        "type": "string",
        "enum": [
          "queued",
          "running",
          "passed",
          "failed",
          "flaky",
          "cancelled",
          "errored"
        ]
      },
      "CreateRunRequest": {
        "type": "object",
        "required": [
          "url",
          "prompt"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Start URL. Cloud runs need a verified domain on qaitai Cloud.",
            "title": "maxLength(2048)",
            "maxLength": 2048,
            "format": "uri"
          },
          "prompt": {
            "type": "string",
            "description": "The journey to test, in plain English (10-8000 characters).",
            "title": "maxLength(8000)",
            "maxLength": 8000,
            "minLength": 10
          },
          "projectId": {
            "type": "string",
            "description": "Project to file the run under. Optional when the workspace has one project, or one whose app URL has the same host as `url`.",
            "title": "maxLength(64)",
            "minLength": 1,
            "maxLength": 64
          },
          "workers": {
            "type": "integer",
            "description": "Parallel workers (default 1).",
            "title": "between(1, 10)",
            "minimum": 1,
            "maximum": 10
          },
          "model": {
            "type": "string",
            "description": "Model id (default claude-sonnet-5).",
            "title": "maxLength(100)",
            "minLength": 1,
            "maxLength": 100
          },
          "keyMode": {
            "type": "string",
            "enum": [
              "managed",
              "byok"
            ],
            "description": "Default: byok when the workspace has a model key connected, else managed."
          },
          "runtime": {
            "type": "string",
            "enum": [
              "cloud",
              "self_hosted"
            ],
            "description": "Default: self_hosted for private/localhost URLs, else cloud."
          },
          "gitRef": {
            "type": "string",
            "description": "e.g. \"main@a41e2d\" or \"pr/412\".",
            "title": "maxLength(200)",
            "maxLength": 200
          }
        },
        "additionalProperties": false
      },
      "RunPage": {
        "type": "object",
        "required": [
          "data",
          "nextCursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Run"
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Pass as `cursor` for the next page; null on the last page."
          }
        },
        "additionalProperties": false
      },
      "RunDetail": {
        "type": "object",
        "required": [
          "id",
          "projectId",
          "suiteId",
          "kind",
          "status",
          "prompt",
          "targetUrl",
          "gitRef",
          "workerCount",
          "maxSteps",
          "maxMinutes",
          "runtime",
          "keyMode",
          "model",
          "createdBy",
          "rerunOf",
          "createdAt",
          "startedAt",
          "finishedAt",
          "url",
          "workers",
          "issues",
          "issueCounts"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "suiteId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "agent",
              "replay"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "prompt": {
            "type": "string"
          },
          "targetUrl": {
            "type": "string"
          },
          "gitRef": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "workerCount": {
            "type": "number"
          },
          "maxSteps": {
            "type": "number",
            "description": "Step limit per worker."
          },
          "maxMinutes": {
            "type": "number",
            "description": "Time limit per worker."
          },
          "runtime": {
            "type": "string",
            "enum": [
              "cloud",
              "self_hosted"
            ]
          },
          "keyMode": {
            "type": "string",
            "enum": [
              "managed",
              "byok"
            ]
          },
          "model": {
            "type": "string"
          },
          "createdBy": {
            "type": "string",
            "description": "Member id, \"github:<login>\", \"schedule:<trigger>\" or \"api_key:<id>\"."
          },
          "rerunOf": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "finishedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "url": {
            "type": "string",
            "description": "The run in the qaitai app."
          },
          "workers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Worker"
            }
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IssueSummary"
            }
          },
          "issueCounts": {
            "type": "object",
            "required": [
              "critical",
              "high",
              "medium",
              "low"
            ],
            "properties": {
              "critical": {
                "type": "number"
              },
              "high": {
                "type": "number"
              },
              "medium": {
                "type": "number"
              },
              "low": {
                "type": "number"
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "Worker": {
        "type": "object",
        "required": [
          "id",
          "label",
          "status",
          "startedAt",
          "finishedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "startedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "finishedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "IssueSummary": {
        "type": "object",
        "required": [
          "id",
          "severity",
          "title",
          "status",
          "createdAt",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/Severity"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "Severity": {
        "type": "string",
        "enum": [
          "critical",
          "high",
          "medium",
          "low"
        ]
      },
      "SuitePage": {
        "type": "object",
        "required": [
          "data",
          "nextCursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Suite"
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Pass as `cursor` for the next page; null on the last page."
          }
        },
        "additionalProperties": false
      },
      "Suite": {
        "type": "object",
        "required": [
          "id",
          "projectId",
          "name",
          "prompt",
          "triggers",
          "mode",
          "workerCount",
          "enabled",
          "scheduleHour",
          "scheduleWeekday",
          "timezone",
          "rerunOnFailure",
          "compiled",
          "lastResult",
          "createdAt",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "triggers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "pr",
                "deploy",
                "nightly",
                "weekly",
                "manual"
              ]
            }
          },
          "mode": {
            "type": "string",
            "enum": [
              "exploratory",
              "replay"
            ]
          },
          "workerCount": {
            "type": "number"
          },
          "enabled": {
            "type": "boolean"
          },
          "scheduleHour": {
            "type": "number"
          },
          "scheduleWeekday": {
            "type": "number"
          },
          "timezone": {
            "type": "string"
          },
          "rerunOnFailure": {
            "type": "boolean"
          },
          "compiled": {
            "type": "boolean",
            "description": "Has compiled replay steps."
          },
          "lastResult": {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "at",
                  "trigger",
                  "status"
                ],
                "properties": {
                  "at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "trigger": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "runId": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "agent",
                      "replay"
                    ]
                  },
                  "reason": {
                    "type": "string"
                  }
                },
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "CreateSuiteRequest": {
        "type": "object",
        "required": [
          "projectId",
          "name",
          "prompt"
        ],
        "properties": {
          "projectId": {
            "type": "string",
            "description": "a string at most 64 character(s) long",
            "title": "maxLength(64)",
            "minLength": 1,
            "maxLength": 64
          },
          "name": {
            "type": "string",
            "description": "a string at most 80 character(s) long",
            "title": "maxLength(80)",
            "maxLength": 80,
            "minLength": 1
          },
          "prompt": {
            "type": "string",
            "description": "The journey to test, in plain English.",
            "title": "maxLength(8000)",
            "maxLength": 8000,
            "minLength": 10
          },
          "triggers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "pr",
                "deploy",
                "nightly",
                "weekly",
                "manual"
              ]
            },
            "description": "Default [\"manual\"].",
            "title": "maxItems(5)",
            "minItems": 1,
            "maxItems": 5
          },
          "mode": {
            "type": "string",
            "enum": [
              "exploratory",
              "replay"
            ],
            "description": "Default exploratory."
          },
          "workerCount": {
            "type": "integer",
            "description": "a number between 1 and 10",
            "title": "between(1, 10)",
            "minimum": 1,
            "maximum": 10
          },
          "scheduleHour": {
            "type": "integer",
            "description": "a number between 0 and 23",
            "title": "between(0, 23)",
            "minimum": 0,
            "maximum": 23
          },
          "scheduleWeekday": {
            "type": "integer",
            "description": "a number between 0 and 6",
            "title": "between(0, 6)",
            "minimum": 0,
            "maximum": 6
          },
          "timezone": {
            "type": "string"
          },
          "rerunOnFailure": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "RunSuiteRequest": {
        "type": "object",
        "required": [],
        "properties": {
          "url": {
            "type": "string",
            "description": "Run against this URL instead of the project's app URL (e.g. a preview deploy).",
            "title": "maxLength(2048)",
            "maxLength": 2048,
            "format": "uri"
          },
          "gitRef": {
            "type": "string",
            "description": "a string at most 200 character(s) long",
            "title": "maxLength(200)",
            "maxLength": 200
          }
        },
        "additionalProperties": false
      },
      "IssuePage": {
        "type": "object",
        "required": [
          "data",
          "nextCursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Issue"
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Pass as `cursor` for the next page; null on the last page."
          }
        },
        "additionalProperties": false
      },
      "Issue": {
        "type": "object",
        "required": [
          "id",
          "projectId",
          "runId",
          "severity",
          "title",
          "description",
          "reproSteps",
          "likelyCause",
          "status",
          "externalUrl",
          "createdAt",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "runId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "severity": {
            "$ref": "#/components/schemas/Severity"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "reproSteps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "likelyCause": {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "note"
                ],
                "properties": {
                  "file": {
                    "type": "string"
                  },
                  "line": {
                    "type": "number"
                  },
                  "note": {
                    "type": "string"
                  }
                },
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string"
          },
          "externalUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ProjectPage": {
        "type": "object",
        "required": [
          "data",
          "nextCursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Project"
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Pass as `cursor` for the next page; null on the last page."
          }
        },
        "additionalProperties": false
      },
      "Project": {
        "type": "object",
        "required": [
          "id",
          "name",
          "repoFullName",
          "defaultUrl",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "repoFullName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "defaultUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      }
    }
  }
}