{
  "openapi": "3.0.3",
  "info": {
    "title": "Lathe Studio API",
    "version": "1.2.0",
    "description": "REST API for integrating CI/CD pipelines, automation frameworks, and external tools with Lathe Studio.\n\nAll endpoints are versioned under `/api/v1`. Authentication uses API keys in Bearer token format.\n\n**Availability:** the API is included in Lathe Studio's single plan, and in the 90-day preview.\n\n**Errors** always use `{ \"error\": { \"code\", \"message\", \"details\"? } }`. Branch on `error.code`, then on `error.details.reason` where present; the code for a given failure never changes.",
    "contact": {
      "email": "support@lathe.studio"
    }
  },
  "servers": [
    {
      "url": "https://lathe.studio/api/v1",
      "description": "Production"
    },
    {
      "url": "http://localhost:3000/api/v1",
      "description": "Local Development"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Builds",
      "description": "Create and query builds. Use the builds API to register new deployments from your CI/CD pipeline and associate them with releases."
    },
    {
      "name": "Test Runs",
      "description": "Query test runs, submit automation results, and split runs into child runs."
    },
    {
      "name": "Projects",
      "description": "List projects and their test cases."
    },
    {
      "name": "Test Cases",
      "description": "Create, update, and delete test cases in the test library."
    },
    {
      "name": "Regression Sets",
      "description": "Manage Regression Sets — reusable collections of test cases and section subscriptions (paths, scopes, and field names use `test_sets` / `test-sets` / `testsets:*`)."
    },
    {
      "name": "Releases",
      "description": "Create, list and read releases, and query release readiness."
    },
    {
      "name": "Discovery",
      "description": "Discover supported webhook events and API metadata."
    },
    {
      "name": "Webhooks",
      "description": "Manage outgoing webhook subscriptions."
    }
  ],
  "paths": {
    "/builds": {
      "post": {
        "operationId": "createBuild",
        "summary": "Create a build",
        "description": "Creates a new build and returns `201 Created`. `release_id` is optional — without it the build lands in the project's \"No release\" bucket; a release is never chosen for you. Duplicate names are never refused: if a build with the same name (case-insensitive) already exists in the same release bucket, the new build is created as \"name (2)\", \"name (3)\", … and the final name is returned. An optional `suffix` makes the stored name \"<name> (<suffix>)\".\n\n**Project resolution:** a project-scoped key uses its project. An org-scoped key MUST send `project_key` (project UUID or project_code) — there is no default project and `repo_url` no longer selects one (breaking change for org-scoped keys, 2026-09-24). `project_key` naming a different project than a project-scoped key → `403`.\n\nIdempotency: send an `Idempotency-Key` header to safely retry. An identical retry returns `200` with the original build (no second insert); the same key with a different body returns `409` (`details.reason: idempotency_key_reused`). Keys expire after 24 hours; a claim whose request died mid-flight is reclaimed after 5 minutes.\n\nTypically called from your CI/CD pipeline immediately after a successful deployment.",
        "tags": [
          "Builds"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "builds:create"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBuildRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal - queued build",
                  "description": "Create a build in the project's \"No release\" bucket.",
                  "value": {
                    "name": "v2.4.1"
                  }
                },
                "full": {
                  "summary": "Full - assigned to release",
                  "description": "Create a build fully assigned to a release with CI metadata, a name suffix (stored as \"v2.4.1 (cloud)\") and an explicit project (required for org-scoped keys).",
                  "value": {
                    "name": "v2.4.1",
                    "build_number": "1042",
                    "commit_sha": "a1b2c3d4e5f6789012345678901234567890abcd",
                    "branch": "main",
                    "repo_url": "https://github.com/acme/backend",
                    "release_id": "6067f547-04a7-5e00-af03-ef78a57f4181",
                    "metadata": {
                      "pipeline_id": "pipeline-456",
                      "triggered_by": "push"
                    },
                    "suffix": "cloud",
                    "project_key": "MOB"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay: a request with the same Idempotency-Key and an identical body was already processed. Returns the original build; no new build is created.",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateBuildResponse"
                },
                "example": {
                  "id": "19cb07a9-3ab5-56c0-a54e-648725bdc6e4",
                  "name": "v2.4.1",
                  "status": "queued",
                  "release_id": "6067f547-04a7-5e00-af03-ef78a57f4181",
                  "source": "api",
                  "message": "Build already created for this Idempotency-Key"
                }
              }
            }
          },
          "201": {
            "description": "Build created (in the given release, or the \"No release\" bucket when release_id is omitted). Check `name` — it may be suffixed if the bucket already had a build with that name.",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateBuildResponse"
                },
                "example": {
                  "id": "19cb07a9-3ab5-56c0-a54e-648725bdc6e4",
                  "name": "v2.4.1",
                  "status": "active",
                  "source": "api",
                  "message": "Build created successfully.",
                  "release_id": "6067f547-04a7-5e00-af03-ef78a57f4181"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_key names a different project than the project-scoped API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "FORBIDDEN",
                    "message": "project_key does not match this API key's project"
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Conflict: the Idempotency-Key was reused with a different request body (`details.reason: idempotency_key_reused`), a request with this key is still in progress (`details.reason: idempotency_request_in_progress`), or the target release is completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "CONFLICT",
                    "message": "Idempotency-Key was already used with a different request body",
                    "details": {
                      "reason": "idempotency_key_reused"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional. 1-255 characters. Retry-safe create: identical retries replay the original response (200) instead of creating a duplicate build; a different body with the same key returns 409.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ]
      },
      "get": {
        "operationId": "listBuilds",
        "summary": "List builds",
        "description": "Returns a paginated list of builds. Filter by project (`project_id` or `project_key` — both sent and disagreeing is a 400) or by release bucket: `release=none` for release-less builds, `release=<uuid>` for one release (must belong to the resolved project, else 404).",
        "tags": [
          "Builds"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "builds:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Filter builds by project ID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "project_key",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Project UUID or project_code (alternative to `project_id`, and the only project selector for org-scoped keys without `project_id`). Resolved inside the key's organization. If both `project_id` and `project_key` are sent and disagree → 400."
          },
          {
            "name": "release",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter by release bucket: `none` returns only release-less builds; a release UUID returns that release's builds (the release must belong to the resolved project — else 404)."
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results per page (max 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of results to skip.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of builds.",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BuildListItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0b9d8c7e-6f5a-4b3c-8d2e-1f0a9b8c7d6e",
                      "name": "v2.4.1",
                      "status": "queued",
                      "release_id": null,
                      "created_at": "2026-09-20T14:03:11Z"
                    }
                  ],
                  "pagination": {
                    "total": 1,
                    "limit": 20,
                    "offset": 0,
                    "has_more": false
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/testruns": {
      "get": {
        "operationId": "listTestRuns",
        "summary": "List test runs",
        "description": "Returns a paginated list of test runs for a project or build.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testruns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Filter by project ID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "build_id",
            "in": "query",
            "description": "Filter by build ID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by run status.",
            "schema": {
              "type": "string",
              "enum": [
                "planned",
                "in_progress",
                "submitted",
                "completed",
                "aborted"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "release_id",
            "in": "query",
            "description": "Only runs in this release.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of test runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TestRun"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "operationId": "createTestRun",
        "summary": "Create a test run",
        "description": "Create a new test run with selected test cases. The run is created in `planned` status and can be executed manually or via automation.\n\nWhen `build_id` is given, the run's release is its build's release: a run created on a build in a release belongs to that release, and a run on a release-less build has no release. Passing both `release_id` and `build_id` where the build's release differs returns **400** `VALIDATION_ERROR` with `details.reason: 'release_build_mismatch'` — an explicit release is never silently overridden.\n\n`assignee_id` and `queue: true` both require the **`testruns:assign`** scope in addition to `testruns:create` (touchless T3, 2026-09-24): assigning or queueing a run is a lead's decision in the UI, and the API mirrors that. An assignee together with `queue: true` is refused (400 `assignee_and_queue`). Keys created before 2026-09-24 do not have the scope — an admin enables it on the key. An assigned run now sends the assignment notifications (email, `testrun.assigned.v1`, `run.assigned` chat trigger) attributed to the API key; previously API-created assigned runs sent nothing.\n\n`assignee_email` (touchless T4, 2026-09-24) is accepted anywhere `assignee_id` is: the email is resolved case-insensitively to a user inside your organization and the run is assigned exactly as with `assignee_id` (same validation, notifications, and `testruns:assign` scope requirement). An email that does not match exactly one assignable org member returns **400** `BAD_REQUEST` (the same code this endpoint has always used for `assignee_id` errors, which now also carry `details.reason`) with `details.reason` one of `assignee_not_found`, `assignee_not_assignable` (member lacks effective `test_run:update` in the project), or `assignee_ambiguous` (the email matches more than one user — disambiguate with `assignee_id`). The response is identical whether the email belongs to a different organization or to no one — the API never confirms which. Sending both fields returns `details.reason: 'assignee_conflict'`. There is no endpoint that lists member emails.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testruns:create"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTestRunRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. One run: the flat `CreateTestRunResponse` with a `Location` header. Several runs (`test_set_ids` with `mode: split_by_set`): `CreateTestRunsResponse`, no `Location` header.",
            "headers": {
              "Location": {
                "description": "URL of the newly created test run.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/CreateTestRunResponse"
                    },
                    {
                      "$ref": "#/components/schemas/CreateTestRunsResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/testruns/{id}": {
      "get": {
        "operationId": "getTestRun",
        "summary": "Get a test run",
        "description": "Returns a single test run by ID.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testruns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Test run detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TestRun"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/testruns/{id}/assign": {
      "post": {
        "operationId": "assignTestRun",
        "summary": "Assign, queue, or unassign a test run",
        "description": "Moves a run between the three assignment states. Exactly one target is required:\n- `{ \"assignee_id\": \"<user id>\" }` — assign or reassign to a person (must be assignable in the project).\n- `{ \"assignee_email\": \"<email>\" }` — the same, by email: resolved case-insensitively inside your organization (touchless T4). An email that does not match exactly one assignable org member returns **400** `VALIDATION_ERROR` (`details.reason`: `assignee_not_found`, `assignee_not_assignable`, or `assignee_ambiguous`) — the identical response whether the email belongs to another org or to no one. Sending both assignee fields returns `details.reason: 'assignee_conflict'`.\n- `{ \"queue\": true }` — release the run into the unassigned queue for testers to grab.\n- `{ \"unassigned\": true }` — pull the run back to plain unassigned.\n\nSends the same notifications as assigning in the UI: the `testrun.assigned.v1` webhook, an email to the assignee, and the `run.assigned` Slack/Teams trigger — attributed to the API key by name (the audit row records the key, not a user). Repeating the run's current target returns **200** with `changed: false` and no side effects. An API key cannot grab a run for itself; self-claim remains a browser-only action.\n\n**Completed releases freeze assignment:** if the run's release is `completed`, every target is refused with **409** `details.reason: 'release_completed'` — a completed release freezes the run's assignment.\n\n**Behaviour change (2026-09-24):** creating a run with `assignee_id` (POST /testruns) and splitting a run with an assignee now send the same notifications.\n\nOther `details.reason` values on **400** `VALIDATION_ERROR`: `invalid_target` (no target, or more than one), `assignee_and_queue` (an assignee together with `queue`), `invalid_assignee_email` (malformed email).",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testruns:assign"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "assignee_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Internal user ID to assign the run to."
                      }
                    },
                    "required": [
                      "assignee_id"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "assignee_email": {
                        "type": "string",
                        "format": "email",
                        "description": "Email address of the person to assign the run to, resolved case-insensitively inside your organization (touchless T4)."
                      }
                    },
                    "required": [
                      "assignee_email"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "queue": {
                        "type": "boolean",
                        "const": true,
                        "description": "Release the run into the unassigned queue."
                      }
                    },
                    "required": [
                      "queue"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "unassigned": {
                        "type": "boolean",
                        "const": true,
                        "description": "Pull the run back to plain unassigned."
                      }
                    },
                    "required": [
                      "unassigned"
                    ]
                  }
                ],
                "description": "Exactly one of assignee_id, assignee_email, queue: true, or unassigned: true."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The run's resulting assignment state. `changed` is false when the run was already in the requested state (no side effects).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "assigned_to": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "in_unassigned_queue": {
                      "type": "boolean"
                    },
                    "changed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Conflict: the run's release is completed, so its assignment cannot be changed (`details.reason: release_completed`). Returned for every target, including one equal to the run's current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "CONFLICT",
                    "message": "This run belongs to a completed release, so its assignment can't be changed",
                    "details": {
                      "reason": "release_completed"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/testruns/{id}/split": {
      "post": {
        "operationId": "splitTestRun",
        "summary": "Split a test run",
        "description": "Moves unexecuted cases from a run into one or more child runs. Executed cases stay locked in the original run, whose status becomes `superseded` when emptied.\n\n- `by_section`: one child run per section.\n- `by_set`: one child run per source regression set.\n- `manual`: moves the executions listed in `execution_ids` into a single child run (`execution_ids` is required for this strategy).\n\n`assignee_id` (or `assignee_email`, resolved case-insensitively inside your organization — touchless T4) is optional; when provided, the moved executions are assigned to that user (who must be assignable in the project). Supplying `assignee_id` also requires the `testruns:assign` scope (403 without it), and sends the same assignment notifications as `POST /testruns/{id}/assign`.\n\nA project-scoped key used on another project's run gets **403** `FORBIDDEN` here (assign returns 404 for the same case). Assignee failures return **400** `VALIDATION_ERROR` with the same `details.reason` values as assign.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testruns:create"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SplitTestRunRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Split result: IDs of the created child runs and the original run's resulting status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SplitTestRunResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Conflict — the run cannot be split in its current state (e.g. it is not planned/in_progress or has no unexecuted cases), or its release is completed (`details.reason: release_completed`) — splitting creates runs, and run creation is refused for a completed release.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/testruns/{id}/results": {
      "get": {
        "operationId": "getTestRunResults",
        "summary": "Get test run results",
        "description": "Returns all execution results for a test run, including pass/fail status, notes, and defect references.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "results:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          },
          {
            "name": "result",
            "in": "query",
            "description": "Only executions with this result. An unknown value returns an empty list.",
            "schema": {
              "type": "string",
              "enum": [
                "pass",
                "fail",
                "blocked",
                "skipped",
                "retest"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of execution results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TestRunResult"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "operationId": "submitTestRunResults",
        "summary": "Submit automation results",
        "description": "Submit execution results from an automation framework (Playwright, Cypress, pytest …). One execution row is added per item. A `test_case_id` that isn't a live case in the run's project is skipped and counted in `skipped`, not rejected. This endpoint does not honour `Idempotency-Key`.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "results:submit"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "results"
                ],
                "properties": {
                  "results": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "test_case_id",
                        "result"
                      ],
                      "properties": {
                        "test_case_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The Lathe Studio test case ID."
                        },
                        "result": {
                          "type": "string",
                          "enum": [
                            "pass",
                            "fail",
                            "blocked",
                            "skipped",
                            "retest"
                          ],
                          "description": "Execution result."
                        },
                        "duration_seconds": {
                          "type": "integer",
                          "minimum": 0,
                          "description": "Seconds taken; stored on the execution."
                        },
                        "comments": {
                          "type": "string",
                          "maxLength": 5000,
                          "description": "Notes or failure details."
                        }
                      }
                    },
                    "minItems": 1,
                    "maxItems": 1000
                  }
                }
              },
              "example": {
                "results": [
                  {
                    "test_case_id": "c3864dd3-7451-5692-8d98-97b586148f84",
                    "result": "pass",
                    "duration_seconds": 1
                  },
                  {
                    "test_case_id": "bda1d531-3d0d-557a-8d3c-77088e4e9122",
                    "result": "fail",
                    "duration_seconds": 3,
                    "comments": "AssertionError: expected 200 but got 500"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Results submitted successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accepted": {
                      "type": "integer",
                      "description": "Number of results accepted."
                    },
                    "skipped": {
                      "type": "integer",
                      "description": "Number of results skipped (test case not in run)."
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "accepted",
                    "skipped",
                    "message"
                  ]
                },
                "example": {
                  "accepted": 42,
                  "skipped": 1,
                  "message": "Results submitted successfully. 42 accepted, 1 skipped."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The run has already been submitted or completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/testruns/{id}/results/{executionId}/notes": {
      "get": {
        "operationId": "getExploratorySessionNotes",
        "summary": "Get exploratory session notes",
        "description": "Returns the note log of one execution of an exploratory test case (the 'session'), oldest first. Read-only; notes are added from the run page.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "results:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "executionId",
            "in": "path",
            "required": true,
            "description": "The execution id, as returned by GET /testruns/{id}/results.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of session notes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExploratorySessionNote"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "List projects",
        "description": "Returns all projects the API key has access to.",
        "tags": [
          "Projects"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "projects:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of projects.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/projects/{id}/testcases": {
      "get": {
        "operationId": "listTestCases",
        "summary": "List test cases",
        "description": "Returns test cases for a project. Useful for syncing test case IDs with your automation framework.",
        "tags": [
          "Projects"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "projects:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by test case status.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "review",
                "approved",
                "obsolete"
              ]
            }
          },
          {
            "name": "section_id",
            "in": "query",
            "description": "Filter by section ID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of test cases.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TestCase"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "operationId": "createTestCase",
        "summary": "Create a test case",
        "description": "Creates a new test case in the project and section given in the URL path. Steps may mix plain manual steps with tool steps (`step_type` + `tool_config`).\n\nThe case-level `preconditions` field is **deprecated**: when non-empty it is converted into a leading `step_type: 'precondition'` step and never stored in a column. Send a precondition step directly instead. Accepts an optional `case_type` — the vendor-style case Type (functional, regression, smoke, acceptance, …); omitted or null means not set. Unknown values are rejected with a 400 VALIDATION_ERROR.",
        "tags": [
          "Test Cases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testcases:create"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTestCaseRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Test case created.",
            "headers": {
              "Location": {
                "description": "URL of the newly created test case.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestCaseWriteResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/projects/{id}/testcases/{tcId}": {
      "get": {
        "operationId": "getTestCase",
        "summary": "Get a test case",
        "description": "Returns one test case with its `custom_fields` serialized to the P6 wire format (see GET /projects/{id}/custom-fields for the schema). Values whose definition has been archived are omitted.",
        "tags": [
          "Test Cases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "projects:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tcId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The test case.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestCaseDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "put": {
        "operationId": "updateTestCase",
        "summary": "Update a test case",
        "description": "Partial update of a test case. All fields are optional; unknown fields are rejected. At least one field must be provided.\n\nWhen `preconditions` is provided without `steps`, the deprecated conversion applies against the case's stored steps. Pass `case_type` to set the vendor-style case Type, or `null` to clear it back to not set.",
        "tags": [
          "Test Cases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testcases:update"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tcId",
            "in": "path",
            "required": true,
            "description": "Test case ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTestCaseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Test case updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestCaseWriteResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "delete": {
        "operationId": "deleteTestCase",
        "summary": "Delete a test case",
        "description": "Soft-deletes a test case. Idempotent — returns 204 even if the case was already deleted.",
        "tags": [
          "Test Cases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testcases:delete"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tcId",
            "in": "path",
            "required": true,
            "description": "Test case ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Test case deleted. No response body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/projects/{id}/custom-fields": {
      "get": {
        "operationId": "listCustomFields",
        "summary": "List project custom fields",
        "description": "Returns the project's ACTIVE custom field definitions in display (`position`) order: `key`, `label`, `field_type`, `options` (option labels, in order — null for non-select types), `position`. Archived definitions are omitted. There is no API to create, edit or archive definitions in v1 — managing fields is a human task in Project Settings.",
        "tags": [
          "Test Cases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "projects:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Active custom field definitions, in position order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string",
                            "example": "severity"
                          },
                          "label": {
                            "type": "string",
                            "example": "Severity"
                          },
                          "field_type": {
                            "type": "string",
                            "enum": [
                              "text",
                              "long_text",
                              "number",
                              "select",
                              "multi_select",
                              "checkbox",
                              "date",
                              "url"
                            ]
                          },
                          "options": {
                            "type": "array",
                            "nullable": true,
                            "items": {
                              "type": "string"
                            }
                          },
                          "position": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/projects/{id}/test-sets": {
      "get": {
        "operationId": "listRegressionSets",
        "summary": "List Regression Sets",
        "description": "Returns the project's Regression Sets (reusable collections of test cases and section subscriptions) with their current item counts.",
        "tags": [
          "Regression Sets"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testsets:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results per page (max 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of results to skip.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of Regression Sets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RegressionSetListItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "operationId": "createRegressionSet",
        "summary": "Create a Regression Set",
        "description": "Creates a Regression Set from case items and/or section items. `case` items are static snapshots; `section` items become dynamic subscriptions — test cases added to a subscribed section join in-flight runs automatically.\n\nRequires the `testsets:create` scope.",
        "tags": [
          "Regression Sets"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testsets:create"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRegressionSetRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Regression Set created.",
            "headers": {
              "Location": {
                "description": "URL of the newly created Regression Set.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegressionSetListItem"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — e.g. unknown fields, or case/section IDs that do not belong to the project (`Invalid case IDs: ...` / `Invalid section IDs: ...`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/projects/{id}/test-sets/{setId}": {
      "get": {
        "operationId": "getRegressionSet",
        "summary": "Get a Regression Set",
        "description": "Returns a single Regression Set with its items (cases and section subscriptions), ordered by position.",
        "tags": [
          "Regression Sets"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testsets:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "description": "Regression Set ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Regression Set detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegressionSetDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "put": {
        "operationId": "updateRegressionSet",
        "summary": "Update a Regression Set",
        "description": "Updates a Regression Set's name and/or description. When `items` is provided it fully replaces the set's items.\n\nRequires the `testsets:update` scope.",
        "tags": [
          "Regression Sets"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testsets:update"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "description": "Regression Set ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRegressionSetRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Regression Set updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegressionSetListItem"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — e.g. case/section IDs that do not belong to the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "delete": {
        "operationId": "deleteRegressionSet",
        "summary": "Delete a Regression Set",
        "description": "Soft-deletes a Regression Set. A set that doesn't exist, or was already deleted, returns **404**.\n\nRequires the `testsets:delete` scope.",
        "tags": [
          "Regression Sets"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testsets:delete"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "description": "Regression Set ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Regression Set deleted. No response body."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/projects/{id}/test-sets/{setId}/preview": {
      "get": {
        "operationId": "getRegressionSetPreview",
        "summary": "Preview a resolved Regression Set",
        "description": "Returns the set resolved against the current test case library: the total current case count plus up to 5 sample cases. Section items are expanded at request time, so the result reflects the library as it is now — not when the set was created.",
        "tags": [
          "Regression Sets"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testsets:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "description": "Regression Set ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resolved Regression Set preview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegressionSetPreview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/releases/{id}/progress": {
      "get": {
        "operationId": "getReleaseProgress",
        "summary": "Get release readiness",
        "description": "Readiness for a release: counts of verified, pending, blocked and new test cases across its builds. Requires the `testruns:read` scope.",
        "tags": [
          "Releases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "testruns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Release readiness summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseProgress"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "List webhook event types",
        "description": "Lists the webhook event types and a human-readable description of each payload. No authentication; this endpoint cannot fail.",
        "tags": [
          "Discovery"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Supported event types.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events",
                    "docs_url",
                    "openapi_url"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "name",
                          "description",
                          "payload"
                        ],
                        "properties": {
                          "name": {
                            "type": "string",
                            "example": "testrun.submitted.v1"
                          },
                          "description": {
                            "type": "string"
                          },
                          "payload": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "Field name → a short description of its type (not a JSON Schema)."
                          }
                        }
                      }
                    },
                    "docs_url": {
                      "type": "string",
                      "example": "/docs/api/webhooks"
                    },
                    "openapi_url": {
                      "type": "string",
                      "example": "/openapi.json"
                    }
                  }
                },
                "example": {
                  "events": [
                    {
                      "name": "testrun.submitted.v1",
                      "description": "Fired when a tester submits a test run.",
                      "payload": {
                        "run_id": "string (UUID)",
                        "submitted_by": "string (Clerk user ID)"
                      }
                    }
                  ],
                  "docs_url": "/docs/api/webhooks",
                  "openapi_url": "/openapi.json"
                }
              }
            }
          }
        }
      }
    },
    "/results/ingest": {
      "post": {
        "operationId": "ingestResults",
        "summary": "Ingest automated test results",
        "description": "Accepts JSON test results from any CI/CD pipeline. Refers to a Build by name (results attach to the NEWEST non-deleted build of that name in the project, case-insensitive) and creates one in \"No release\" only when none exists — a release is never guessed. Creates a completed Test Run, links results to existing Test Cases (by display code or external_id), or auto-creates new Test Cases for unknown identifiers. Pass `build.release_id` to target a bucket, `build.suffix` for \"name (suffix)\".\n\n**409** `CONFLICT` (`details.reason: release_completed`) when the build's release is completed. Validation failures are **400** `BAD_REQUEST` with `details.field`.",
        "tags": [
          "Test Runs"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "results:submit"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "build",
                  "results"
                ],
                "properties": {
                  "build": {
                    "type": "object",
                    "required": [
                      "identifier"
                    ],
                    "properties": {
                      "identifier": {
                        "type": "string",
                        "description": "Build name or version (e.g. v2.4.1)"
                      },
                      "branch": {
                        "type": "string"
                      },
                      "commit_sha": {
                        "type": "string"
                      },
                      "url": {
                        "type": "string",
                        "description": "CI build URL"
                      },
                      "suffix": {
                        "type": "string",
                        "description": "Optional (D6): the stored build name becomes \"<identifier> (<suffix>)\"."
                      },
                      "release_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Optional: restrict the build lookup to this release's bucket and place a created build in it. Never guessed when omitted."
                      }
                    }
                  },
                  "project_key": {
                    "type": "string",
                    "description": "Project UUID or project code. Required for org-scoped API keys."
                  },
                  "source": {
                    "type": "object",
                    "properties": {
                      "pipeline": {
                        "type": "string"
                      },
                      "run_id": {
                        "type": "string"
                      },
                      "run_url": {
                        "type": "string"
                      }
                    }
                  },
                  "results": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "test_identifier",
                        "status"
                      ],
                      "properties": {
                        "test_identifier": {
                          "type": "string",
                          "description": "Display code (TC-PROJ-0042) or external_id"
                        },
                        "name": {
                          "type": "string",
                          "description": "Test case title (used when auto-creating)"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "passed",
                            "failed",
                            "error",
                            "skipped"
                          ]
                        },
                        "duration_ms": {
                          "type": "integer"
                        },
                        "error": {
                          "type": "string"
                        },
                        "stdout": {
                          "type": "string"
                        },
                        "file": {
                          "type": "string"
                        },
                        "line": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "auto_create": {
                    "type": "boolean",
                    "default": true,
                    "description": "When true (default), unknown test_identifiers are auto-created as test cases. When false, they are returned in results_unmatched and no test cases are created."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Results ingested successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "build_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "build_display_id": {
                      "type": "string",
                      "description": "The `build.identifier` you sent (without any suffix)."
                    },
                    "test_run_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "test_run_display_id": {
                      "type": "string",
                      "nullable": true,
                      "example": "RUN-MOB-0042"
                    },
                    "test_run_url": {
                      "type": "string"
                    },
                    "test_cases_created": {
                      "type": "integer"
                    },
                    "test_cases_linked": {
                      "type": "integer"
                    },
                    "results_imported": {
                      "type": "integer",
                      "description": "Results received, counted before duplicates for the same case are merged."
                    },
                    "results_unmatched": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "test_identifier values that could not be linked. Empty unless auto_create is false."
                    }
                  },
                  "required": [
                    "build_id",
                    "build_display_id",
                    "test_run_id",
                    "test_run_display_id",
                    "test_run_url",
                    "test_cases_created",
                    "test_cases_linked",
                    "results_imported",
                    "results_unmatched"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "operationId": "whoami",
        "summary": "Get current API key metadata",
        "description": "Returns metadata for the API key used to authenticate the request. Any valid API key can call this endpoint.",
        "tags": [
          "Discovery"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "API key metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "org_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "project_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "rate_limit_per_minute": {
                      "type": "integer"
                    },
                    "monthly_quota_override": {
                      "type": "integer",
                      "nullable": true
                    },
                    "monthly_usage": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/organizations": {
      "get": {
        "operationId": "listOrganizations",
        "summary": "Get your organization",
        "description": "Returns the organization that owns the API key: plan, preview days left, lock state, billing and usage. A project-scoped key returns its parent organization.",
        "tags": [
          "Discovery"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "projects:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The organization.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Organization"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5e8f2a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
                    "name": "Acme",
                    "plan": "trial",
                    "trialDaysLeft": 41,
                    "isPaid": false,
                    "isExempt": false,
                    "lockState": "active",
                    "isExpired": false,
                    "billing": {
                      "model": "per_user",
                      "pricePerSeat": 25,
                      "monthlyTotal": 0,
                      "note": "Trial — no charge"
                    },
                    "usage": {
                      "currentUsers": 3,
                      "currentProjects": 2,
                      "currentActiveReleases": 1
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/openapi": {
      "get": {
        "operationId": "getOpenApiSpec",
        "summary": "Get OpenAPI specification",
        "description": "Redirects to the static OpenAPI JSON spec at `/openapi.json`.",
        "tags": [
          "Discovery"
        ],
        "security": [],
        "responses": {
          "302": {
            "description": "Redirect to `/openapi.json`."
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhook subscriptions",
        "description": "Returns all active webhook subscriptions for the organization.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "List of webhook subscriptions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookSubscription"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Create webhook subscription",
        "description": "Register a new webhook endpoint. The signing secret is returned once — save it immediately.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "HTTPS endpoint URL"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Event types to subscribe to"
                  },
                  "description": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created. The secret is shown only once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "secret"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookSubscription"
                    },
                    "secret": {
                      "type": "string",
                      "description": "Signing secret (`whsec_` + 64 hex characters). Returned only here; store it now.",
                      "example": "whsec_0000000000000000000000000000000000000000000000000000000000000000"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete webhook subscription",
        "description": "Soft-deletes a webhook subscription. No further events will be delivered.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/releases": {
      "get": {
        "operationId": "listReleases",
        "summary": "List releases",
        "description": "Returns a paginated list of a project's releases, newest first.\n\n**Project resolution (D7):** a project-scoped key lists its own project. An org-scoped key MUST send `project_key` (project UUID or project_code) — there is no default project (400 without one).\n\nOptional `status` filter: `planned`/`active`/`completed`.",
        "tags": [
          "Releases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "releases:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "project_key",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Project UUID or project_code (required for org-scoped keys)."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "planned",
                "active",
                "completed"
              ]
            },
            "description": "Filter by release status."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of releases.",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Release"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "operationId": "createRelease",
        "summary": "Create a release",
        "description": "Creates a new release and returns `201 Created` with a `Location` header.\n\n**Project resolution (D7):** a project-scoped key creates in its own project; an org-scoped key MUST send `project_key`.\n\n**Status:** `planned` (default) or `active`. `completed` is refused — creating an already-completed release is completing one, and completing is human-only (400 `details.reason: release_status_not_allowed`).\n\n**Names:** duplicate names (case-insensitive) are refused with **409** `details.reason: release_name_taken`. Unlike builds, releases are never auto-suffixed.\n\n**Idempotency:** an `Idempotency-Key` header retries safely — an identical retry returns `200` with the original release (no second insert); the same key with a different body returns `409`.\n\n**Dates:** `target_date` must be today or later.",
        "tags": [
          "Releases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "releases:create"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateReleaseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay: the release created by an earlier request with the same `Idempotency-Key` and body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateReleaseResponse"
                }
              }
            }
          },
          "201": {
            "description": "Release created.",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "Path of the new release: /api/v1/releases/{id}."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateReleaseResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/releases/{id}": {
      "get": {
        "operationId": "getRelease",
        "summary": "Get a release",
        "description": "Returns a release with its `build_count` (non-deleted builds currently in the release). A project-scoped key only sees its own project's releases; anything else is a 404. There is no endpoint that updates a release's status or deletes it — completing and deleting are human-only.",
        "tags": [
          "Releases"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "releases:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Release UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "The release with build_count.",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/builds/{id}": {
      "get": {
        "operationId": "getBuild",
        "summary": "Get a build",
        "description": "Returns a single build by ID. A project-scoped key only sees its own project's builds; anything else is a 404. This is where the `Location` header from `POST /builds` points.",
        "tags": [
          "Builds"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "builds:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Build UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "The build.",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Build"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "patch": {
        "operationId": "placeBuild",
        "summary": "Place a build in a release",
        "description": "Moves a build into a release, or back to the project's \"No release\" bucket with `release_id: null`. The body is exactly `{ \"release_id\": \"<uuid>\" }` — any other field is a 400, and no other build field is editable through the API.\n\nThe move runs through the same core as the UI's *Place in release*: the build's runs follow it in the database, a name collision in the target bucket suffixes the name (\"X (2)\") instead of refusing, and a **completed** release refuses the move in either direction (**409** `details.reason: release_completed`). A release from another project is a 404.\n\nReturns the build (`id`, `name`, `release_id`, `status`).",
        "tags": [
          "Builds"
        ],
        "security": [
          {
            "ApiKeyAuth": [
              "builds:update"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Build UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BuildPlacementRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The build after the move (name may be suffixed).",
            "headers": {
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BuildPlacement"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The build's current or target release is completed (details.reason: release_completed)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key in format `pt_live_<hex>`. Obtain from **Org Settings -> API Keys**.\n\nInclude in every request:\n```\nAuthorization: Bearer pt_live_abc123...\n```\n\n**Scopes** are embedded in the key at creation time. A key with `builds:create` can create builds but not read test runs. Scopes cannot be changed after key creation."
      }
    },
    "schemas": {
      "Build": {
        "type": "object",
        "description": "A build: a specific version of your software, optionally placed in a release. CI metadata (commit, branch, build number) is stored with the build but not returned by the API.",
        "required": [
          "id",
          "project_id",
          "name",
          "status",
          "release_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "v2.4.1"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "planned",
              "active",
              "released"
            ],
            "description": "Build status. CI arrivals are `queued`; an unknown status is stored as `queued`."
          },
          "release_id": {
            "type": "string",
            "format": "uuid",
            "description": "`null` = the project's \"No release\" bucket.",
            "nullable": true
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "CreateBuildRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Build name or version string. Typically the semver tag or build number. Max 100 characters; a caller-supplied suffix counts toward the same limit.",
            "example": "v2.4.1",
            "minLength": 1,
            "maxLength": 100
          },
          "build_number": {
            "type": "string",
            "description": "CI/CD build number for cross-referencing.",
            "example": "1042"
          },
          "commit_sha": {
            "type": "string",
            "pattern": "^[a-fA-F0-9]{7,40}$",
            "description": "Git commit SHA, 7–40 hex characters.",
            "example": "a1b2c3d"
          },
          "branch": {
            "type": "string",
            "description": "Git branch that was built.",
            "example": "main"
          },
          "repo_url": {
            "type": "string",
            "description": "Repository URL for deep-linking. Metadata only — it no longer selects a project for org-scoped keys (breaking change, 2026-09-24); use project_key.",
            "example": "https://github.com/acme/backend"
          },
          "release_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the release this build belongs to. Optional: when omitted the build lands in the project's \"No release\" bucket. The release must exist in the same project (404 otherwise) and must not be completed (409 otherwise)."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Arbitrary key/value metadata (pipeline ID, triggered_by, environment, etc.).",
            "example": {
              "pipeline_id": "pipeline-456",
              "triggered_by": "push"
            }
          },
          "suffix": {
            "type": "string",
            "description": "Optional name suffix (D6): the stored name becomes \"<name> (<suffix>)\", e.g. \"abc123 (cloud)\". From then on that IS the name for duplicate-suffixing and reporting. Trimmed; the combined name must fit within 100 characters (400 otherwise).",
            "example": "cloud",
            "minLength": 1,
            "maxLength": 50
          },
          "project_key": {
            "type": "string",
            "description": "Project UUID or project_code. Required when using an org-scoped API key; optional for a project-scoped key, and it must name the same project (403 otherwise). Replaces the removed repo_url project lookup.",
            "example": "MOB",
            "minLength": 1,
            "maxLength": 100
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "planned",
              "active",
              "released"
            ],
            "description": "Build status. CI arrivals are `queued`; an unknown status is stored as `queued`.",
            "default": "queued"
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "description": "Must be on or before `end_date`."
          },
          "end_date": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "CreateBuildResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Newly created build ID."
          },
          "name": {
            "type": "string",
            "description": "The final stored name. If a build with this name already exists in the release bucket, the name is suffixed: \"X\", then \"X (2)\", \"X (3)\"…"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "planned",
              "active",
              "released"
            ],
            "description": "Build status. CI arrivals are `queued`; an unknown status is stored as `queued`."
          },
          "source": {
            "type": "string",
            "enum": [
              "api"
            ],
            "description": "Always `api` for builds created through this endpoint."
          },
          "message": {
            "type": "string",
            "description": "Human-readable status message."
          },
          "release_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Release the build belongs to, or null when created in the \"No release\" bucket."
          }
        }
      },
      "CreateTestRunRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Test run name.",
            "example": "Sprint 14 Regression",
            "minLength": 1
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "description": "Project ID. Required when using an org-scoped API key."
          },
          "test_case_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "IDs of test cases to include in this run. Provide either `test_case_ids` or `test_set_ids`, not both.",
            "minItems": 1
          },
          "test_set_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "IDs of test sets to include in this run. Provide either `test_case_ids` or `test_set_ids`, not both.",
            "minItems": 1
          },
          "mode": {
            "type": "string",
            "enum": [
              "unified",
              "split_by_set"
            ],
            "default": "unified",
            "description": "When using `test_set_ids`, `unified` creates one run from all sets, `split_by_set` creates one run per set."
          },
          "environment": {
            "type": "string",
            "description": "Execution environment.",
            "example": "staging"
          },
          "custom_environment": {
            "type": "string",
            "nullable": true,
            "description": "Custom environment name when environment is `custom`."
          },
          "release_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Associate this run with a release."
          },
          "build_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Associate this run with a build."
          },
          "due_date": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Optional due date as an ISO 8601 datetime."
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description or notes."
          },
          "assignee_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Optional assignee (internal user ID). The user must be assignable in the project (effective test_run:update permission). The assignee is notified (email, testrun.assigned.v1 webhook, run.assigned chat trigger) attributed to the API key. Requires the `testruns:assign` scope in addition to `testruns:create`."
          },
          "assignee_email": {
            "type": "string",
            "format": "email",
            "nullable": true,
            "description": "Optional assignee by email address, resolved case-insensitively to a user inside your organization (touchless T4). Alternative to `assignee_id` — sending both returns **400** `BAD_REQUEST` with `details.reason: 'assignee_conflict'`. Same assignability validation, notifications, and `testruns:assign` scope requirement as `assignee_id`."
          },
          "queue": {
            "type": "boolean",
            "description": "Release the run into the unassigned queue instead of assigning it. Cannot be combined with an assignee (`assignee_id` or `assignee_email`) (**400** `VALIDATION_ERROR`, `details.reason: 'assignee_and_queue'`). Requires the `testruns:assign` scope in addition to `testruns:create`.",
            "default": false
          }
        }
      },
      "CreateTestRunResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Newly created test run ID."
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "planned"
            ],
            "description": "A new run is always `planned`."
          },
          "display_code": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable run code, e.g. RUN-PROJ-0042."
          },
          "source": {
            "type": "string",
            "enum": [
              "api"
            ],
            "description": "Always `api` for runs created through this endpoint."
          },
          "message": {
            "type": "string",
            "description": "Human-readable status message."
          }
        }
      },
      "TestRun": {
        "type": "object",
        "description": "A test run is an execution session for a set of test cases against a specific build.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "release_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "build_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "assigned_to": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Internal user ID of the assignee. null when unassigned."
          },
          "in_unassigned_queue": {
            "type": "boolean",
            "description": "True when an unassigned run has been released for tester pickup (unassigned queue). Always false when assigned_to is set. Read-only in this API version; runs created via POST land as plain unassigned (the lead's triage)."
          },
          "name": {
            "type": "string",
            "example": "Regression - v2.4.1 Staging"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "environment": {
            "type": "string",
            "description": "Free text as sent when the run was created (commonly `dev`, `staging`, `production`); `custom` when none was given.",
            "example": "staging",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "planned",
              "in_progress",
              "submitted",
              "completed",
              "aborted",
              "superseded"
            ]
          },
          "inheritance_policy": {
            "type": "string",
            "enum": [
              "strict",
              "lenient",
              "none"
            ],
            "default": "lenient"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "custom_environment": {
            "type": "string",
            "nullable": true
          },
          "run_sequence_number": {
            "type": "integer",
            "description": "Per-project run counter; the number in `display_code`."
          },
          "display_code": {
            "type": "string",
            "example": "RUN-MOB-0042",
            "nullable": true
          }
        },
        "required": [
          "id",
          "project_id",
          "name",
          "status",
          "created_at",
          "in_unassigned_queue"
        ]
      },
      "TestRunResult": {
        "type": "object",
        "description": "One test case execution within a test run.",
        "required": [
          "id",
          "result",
          "requires_retest"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "result": {
            "type": "string",
            "enum": [
              "pass",
              "fail",
              "blocked",
              "skipped",
              "retest"
            ],
            "description": "`null` = not executed yet.",
            "nullable": true
          },
          "effective_status": {
            "type": "string",
            "enum": [
              "verified",
              "pending",
              "blocked",
              "new"
            ],
            "description": "The inheritance engine's verdict across builds; `null` until computed.",
            "nullable": true
          },
          "executed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "comments": {
            "type": "string",
            "nullable": true
          },
          "requires_retest": {
            "type": "boolean"
          },
          "inherited_from": {
            "type": "string",
            "format": "uuid",
            "description": "The execution this result was inherited from.",
            "nullable": true
          },
          "inheritance_reason": {
            "type": "string",
            "enum": [
              "no_code_change",
              "manual_override",
              "strict_approved"
            ],
            "nullable": true
          },
          "test_cases": {
            "type": "object",
            "description": "The test case; read its `id` here (there is no top-level `test_case_id`).",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "title": {
                "type": "string"
              },
              "tc_sequence_number": {
                "type": "integer"
              },
              "priority": {
                "type": "string",
                "enum": [
                  "p0",
                  "p1",
                  "p2",
                  "p3"
                ]
              },
              "external_id": {
                "type": "string",
                "nullable": true
              },
              "display_code": {
                "type": "string",
                "example": "TC-MOB-0042",
                "nullable": true
              }
            },
            "nullable": true
          }
        }
      },
      "TestCase": {
        "type": "object",
        "description": "A test case in the test library.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "section_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "title": {
            "type": "string",
            "example": "User can log in with valid credentials"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Your automation framework's test identifier for mapping results."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "approved",
              "obsolete"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "p0",
              "p1",
              "p2",
              "p3"
            ]
          },
          "case_type": {
            "type": "string",
            "enum": [
              "functional",
              "regression",
              "smoke",
              "acceptance",
              "integration",
              "performance",
              "security",
              "usability",
              "accessibility",
              "compatibility",
              "exploratory",
              "other"
            ],
            "nullable": true,
            "description": "Vendor-style case type (TestRail/qase \"Type\") — the classification, not the Standard/Gherkin/Flow format (`test_case_type`). Omitted or null = not set."
          },
          "execution_mode": {
            "type": "string",
            "enum": [
              "manual",
              "automated",
              "hybrid"
            ]
          },
          "automation_status": {
            "type": "string",
            "enum": [
              "not_automated",
              "candidate",
              "in_progress",
              "automated",
              "deprecated"
            ]
          },
          "is_adhoc": {
            "type": "boolean",
            "description": "Whether this test case was created ad-hoc during a test run."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "custom_fields": {
            "type": "object",
            "description": "Project custom field values keyed by field key (P6 wire format). `select` values are option labels, `multi_select` an array of labels, `checkbox` boolean, `number` number, `date` \"YYYY-MM-DD\", text/url strings. Values whose definition has been archived are omitted — the API describes the current schema. Fetch the schema via GET /projects/{id}/custom-fields.",
            "example": {
              "severity": "High",
              "automatable": true,
              "reviewed": "2026-09-20"
            },
            "additionalProperties": true
          },
          "test_case_type": {
            "type": "string",
            "enum": [
              "standard",
              "gherkin",
              "flow"
            ],
            "description": "The case's **Format** in the app (Standard, Gherkin or Flow)."
          },
          "tc_sequence_number": {
            "type": "integer"
          },
          "display_code": {
            "type": "string",
            "example": "TC-MOB-0042",
            "nullable": true
          },
          "gherkin_content": {
            "type": "string",
            "nullable": true,
            "description": "Source Gherkin text when test_case_type is gherkin; steps are derived from it."
          }
        },
        "required": [
          "id",
          "title",
          "status",
          "priority",
          "test_case_type",
          "is_adhoc",
          "custom_fields"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Backend API"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "jira_project_key": {
            "type": "string",
            "nullable": true,
            "example": "BACKEND"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "project_code": {
            "type": "string",
            "description": "Short uppercase code used in display codes.",
            "example": "MOB"
          }
        }
      },
      "TestStep": {
        "type": "object",
        "description": "A single test case step. Plain manual steps carry `order`, `action`, and `expected_result`; tool steps add `step_type` and `tool_config`.",
        "required": [
          "order",
          "action",
          "expected_result"
        ],
        "properties": {
          "order": {
            "type": "integer",
            "minimum": 0,
            "description": "Step position within the case."
          },
          "action": {
            "type": "string",
            "maxLength": 2000,
            "description": "What the tester (or tool) does."
          },
          "expected_result": {
            "type": "string",
            "maxLength": 2000,
            "description": "What should happen. Must be empty for `precondition` steps."
          },
          "step_type": {
            "type": "string",
            "enum": [
              "manual",
              "precondition",
              "verify_regex",
              "compare_field",
              "check_diff",
              "convert_time"
            ],
            "description": "Step kind. `manual` and `precondition` are text-only steps with no `tool_config`. `precondition` carries the precondition text in `action`, requires an empty `expected_result`, and must not have a `tool_config`. The tool step types (`verify_regex`, `compare_field`, `check_diff`, `convert_time`) configure an executable validation rule via `tool_config`."
          },
          "tool_config": {
            "type": "object",
            "additionalProperties": true,
            "description": "Tool-step configuration; the shape depends on `step_type`. `verify_regex`: `pattern`, `flags`, `target_text`, `should_match`. `compare_field`: `source_field`, `target_value`, `operator` (`eq`|`ne`|`gt`|`lt`|`contains`|`starts_with`|`ends_with`). `check_diff`: `expected_text`, `actual_text`, `diff_mode` (`lines`|`words`). `convert_time`: `input_time`, `input_zone`, `output_zone`, `expected_output`. Must be absent for `precondition` steps."
          },
          "shared_step_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Reference to a shared step library entry, if this step is shared."
          }
        }
      },
      "CreateTestCaseRequest": {
        "type": "object",
        "required": [
          "section_id",
          "title"
        ],
        "properties": {
          "section_id": {
            "type": "string",
            "format": "uuid",
            "description": "Section the test case belongs to."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "approved",
              "obsolete"
            ],
            "default": "draft"
          },
          "priority": {
            "type": "string",
            "enum": [
              "p0",
              "p1",
              "p2",
              "p3"
            ],
            "default": "p2"
          },
          "case_type": {
            "type": "string",
            "enum": [
              "functional",
              "regression",
              "smoke",
              "acceptance",
              "integration",
              "performance",
              "security",
              "usability",
              "accessibility",
              "compatibility",
              "exploratory",
              "other"
            ],
            "nullable": true,
            "default": null,
            "description": "Vendor-style case type (TestRail/qase \"Type\") — the classification, not the Standard/Gherkin/Flow format (`test_case_type`). Omitted or null = not set. Unknown values are rejected with a 400 VALIDATION_ERROR."
          },
          "preconditions": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true,
            "deprecated": true,
            "description": "DEPRECATED — accepted for one release, then will be removed. When non-empty, the API converts this value into a leading `step_type: 'precondition'` step; it is never stored in a column. Send a precondition step directly instead."
          },
          "steps": {
            "type": "array",
            "maxItems": 100,
            "default": [],
            "items": {
              "$ref": "#/components/schemas/TestStep"
            },
            "description": "Standard cases only. Gherkin steps are derived from gherkin_content."
          },
          "custom_fields": {
            "type": "object",
            "description": "Project custom field values in the P6 wire format: an object keyed by field key (fetch keys via GET /projects/{id}/custom-fields). `select` values are option labels, `multi_select` an array of labels, `checkbox` boolean, `number` a finite number, `date` \"YYYY-MM-DD\", text/url strings. 400 failures carry `details.reason`: `custom_field_unknown` (key is not an active field of this project, with `details.field` = the key — archived definitions count as unknown), `custom_field_invalid` (value doesn't fit the type or isn't an option), `custom_fields_not_object`. A `null` value is a no-op — no field is required.",
            "additionalProperties": true
          },
          "test_case_type": {
            "type": "string",
            "enum": [
              "standard",
              "gherkin"
            ],
            "description": "Format. Omit for Standard; use gherkin with gherkin_content for Gherkin cases."
          },
          "gherkin_content": {
            "type": "string",
            "maxLength": 100000,
            "description": "Source Gherkin text. Required when creating a Gherkin case. Each Given/When/Then/And/But line becomes a step; steps must not be sent with this field. Scenario Outlines expand to one step block per Examples row, with placeholders substituted."
          }
        }
      },
      "UpdateTestCaseRequest": {
        "type": "object",
        "description": "All fields optional — at least one must be provided. Unknown fields are rejected.",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "section_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "approved",
              "obsolete"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "p0",
              "p1",
              "p2",
              "p3"
            ]
          },
          "case_type": {
            "type": "string",
            "enum": [
              "functional",
              "regression",
              "smoke",
              "acceptance",
              "integration",
              "performance",
              "security",
              "usability",
              "accessibility",
              "compatibility",
              "exploratory",
              "other"
            ],
            "nullable": true,
            "description": "Vendor-style case type (TestRail/qase \"Type\") — the classification, not the Standard/Gherkin/Flow format (`test_case_type`). Omitted or null = not set. Pass null to clear back to not set."
          },
          "preconditions": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true,
            "deprecated": true,
            "description": "DEPRECATED — accepted for one release, then will be removed. When provided without `steps`, the conversion applies against the case's stored steps. Send a precondition step directly instead."
          },
          "steps": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/TestStep"
            },
            "description": "Standard cases only. Gherkin steps are derived from gherkin_content."
          },
          "custom_fields": {
            "type": "object",
            "description": "Project custom field values in the P6 wire format: an object keyed by field key (fetch keys via GET /projects/{id}/custom-fields). `select` values are option labels, `multi_select` an array of labels, `checkbox` boolean, `number` a finite number, `date` \"YYYY-MM-DD\", text/url strings. 400 failures carry `details.reason`: `custom_field_unknown` (key is not an active field of this project, with `details.field` = the key — archived definitions count as unknown), `custom_field_invalid` (value doesn't fit the type or isn't an option), `custom_fields_not_object`. MERGES over the stored values: unspecified keys keep their values; an explicit `null` clears a key.",
            "additionalProperties": true
          },
          "test_case_type": {
            "type": "string",
            "enum": [
              "standard",
              "gherkin"
            ],
            "description": "Format. Omit for Standard; use gherkin with gherkin_content for Gherkin cases."
          },
          "gherkin_content": {
            "type": "string",
            "maxLength": 100000,
            "description": "Source Gherkin text. Required when creating a Gherkin case. Each Given/When/Then/And/But line becomes a step; steps must not be sent with this field. Scenario Outlines expand to one step block per Examples row, with placeholders substituted."
          }
        }
      },
      "TestCaseWriteResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "approved",
              "obsolete"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "p0",
              "p1",
              "p2",
              "p3"
            ]
          },
          "case_type": {
            "type": "string",
            "enum": [
              "functional",
              "regression",
              "smoke",
              "acceptance",
              "integration",
              "performance",
              "security",
              "usability",
              "accessibility",
              "compatibility",
              "exploratory",
              "other"
            ],
            "nullable": true
          },
          "display_code": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable case code, e.g. TC-PROJ-0042."
          },
          "source": {
            "type": "string",
            "enum": [
              "api"
            ],
            "description": "Always `api` for test cases written through this endpoint."
          },
          "message": {
            "type": "string"
          }
        }
      },
      "RegressionSetItemInput": {
        "type": "object",
        "required": [
          "item_type"
        ],
        "properties": {
          "item_type": {
            "type": "string",
            "enum": [
              "case",
              "section"
            ],
            "description": "`case` items are static snapshots of a single test case; `section` items are dynamic subscriptions to every case in the section."
          },
          "case_id": {
            "type": "string",
            "format": "uuid",
            "description": "Required when `item_type` is `case`."
          },
          "section_id": {
            "type": "string",
            "format": "uuid",
            "description": "Required when `item_type` is `section`."
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "default": 0,
            "description": "Display order within the set."
          }
        }
      },
      "CreateRegressionSetRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "items": {
            "type": "array",
            "maxItems": 1000,
            "default": [],
            "items": {
              "$ref": "#/components/schemas/RegressionSetItemInput"
            }
          }
        }
      },
      "UpdateRegressionSetRequest": {
        "type": "object",
        "description": "All fields optional. When `items` is provided it fully replaces the set's items.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "items": {
            "type": "array",
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/RegressionSetItemInput"
            }
          }
        }
      },
      "RegressionSetListItem": {
        "type": "object",
        "description": "A Regression Set (reusable collection of test cases and section subscriptions) as returned by list and write operations.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "item_count": {
            "type": "integer",
            "description": "Number of items (case snapshots + section subscriptions) in the set."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RegressionSetItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Item row ID."
          },
          "item_type": {
            "type": "string",
            "enum": [
              "case",
              "section"
            ]
          },
          "case_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "section_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "position": {
            "type": "integer"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Title of the referenced test case or name of the referenced section."
          }
        }
      },
      "RegressionSetDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RegressionSetItem"
            }
          }
        }
      },
      "RegressionSetPreview": {
        "type": "object",
        "description": "A Regression Set resolved against the current test case library.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "current_case_count": {
            "type": "integer",
            "description": "Total cases the set resolves to right now (section items expanded at request time)."
          },
          "sample_cases": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "title": {
                  "type": "string"
                },
                "display_code": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "SplitTestRunRequest": {
        "type": "object",
        "required": [
          "strategy",
          "name"
        ],
        "properties": {
          "strategy": {
            "type": "string",
            "enum": [
              "by_section",
              "by_set",
              "manual"
            ],
            "description": "`by_section`: one child run per section. `by_set`: one child run per source regression set. `manual`: move the executions in `execution_ids` to a single child run."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Name for the created child run(s)."
          },
          "assignee_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Optional assignee for the moved executions. Must be a user who can be assigned test runs in the project."
          },
          "assignee_email": {
            "type": "string",
            "format": "email",
            "nullable": true,
            "description": "Optional assignee by email address, resolved case-insensitively inside your organization (touchless T4). Alternative to `assignee_id` — sending both returns **400** `VALIDATION_ERROR` with `details.reason: 'assignee_conflict'`."
          },
          "execution_ids": {
            "type": "array",
            "maxItems": 5000,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Execution IDs to move. Required when `strategy` is `manual`."
          }
        }
      },
      "SplitTestRunResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "child_run_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "IDs of the created child runs."
              },
              "original_run_status": {
                "type": "string",
                "description": "The original run's resulting status (`superseded` when all its cases moved, otherwise its current status)."
              }
            }
          }
        }
      },
      "ReleaseProgress": {
        "type": "object",
        "required": [
          "release",
          "builds",
          "progress"
        ],
        "properties": {
          "release": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "planned",
                  "active",
                  "completed"
                ]
              },
              "target_date": {
                "type": "string",
                "format": "date",
                "nullable": true
              },
              "project_id": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          "builds": {
            "type": "array",
            "description": "Up to 100 builds, oldest first.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "queued",
                    "planned",
                    "active",
                    "released"
                  ],
                  "description": "Build status. CI arrivals are `queued`; an unknown status is stored as `queued`."
                }
              }
            }
          },
          "progress": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "verified": {
                "type": "integer"
              },
              "blocked": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              },
              "new": {
                "type": "integer"
              },
              "pass_rate_percent": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100,
                "description": "`round(verified / total * 100)`; 0 when `total` is 0."
              }
            }
          }
        },
        "example": {
          "release": {
            "id": "2f6c1a7e-4b1d-4c55-9a3e-0f6b7c8d9e10",
            "name": "v2.4",
            "status": "active",
            "target_date": "2026-10-15",
            "project_id": "8a1f3c2e-5d4b-4e6a-9b7c-1d2e3f4a5b6c"
          },
          "builds": [
            {
              "id": "0b9d8c7e-6f5a-4b3c-8d2e-1f0a9b8c7d6e",
              "name": "v2.4.0-rc1",
              "status": "active"
            }
          ],
          "progress": {
            "total": 250,
            "verified": 209,
            "blocked": 12,
            "pending": 20,
            "new": 9,
            "pass_rate_percent": 84
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "description": "Total number of records matching the query."
          },
          "limit": {
            "type": "integer",
            "description": "Page size used for this response."
          },
          "offset": {
            "type": "integer",
            "description": "Number of records skipped."
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether there are additional pages available."
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Standard error envelope returned on all 4xx and 5xx responses. Branch on `error.code`, and on `error.details.reason` where present; an endpoint's code for a given failure never changes.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code.",
                "enum": [
                  "BAD_REQUEST",
                  "VALIDATION_ERROR",
                  "INVALID_JSON",
                  "AUTH_REQUIRED",
                  "FORBIDDEN",
                  "RESOURCE_NOT_FOUND",
                  "CONFLICT",
                  "PAYLOAD_TOO_LARGE",
                  "RATE_LIMITED",
                  "INTERNAL_ERROR"
                ],
                "example": "BAD_REQUEST"
              },
              "message": {
                "type": "string",
                "description": "Human-readable description. May change between releases; don't parse it.",
                "example": "Invalid tcId format. Expected UUID."
              },
              "details": {
                "type": "object",
                "description": "Optional context. Depending on the error: `reason` (machine-readable, branch on it), `field` or `fields` (validation), `resource` and `id` (not found), `reset_at` (rate limit).",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "Release": {
        "type": "object",
        "description": "A release groups builds and their test runs. Release names are unique per project (case-insensitive) — a duplicate name is refused with 409, never suffixed.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique release identifier."
          },
          "name": {
            "type": "string",
            "description": "Release name.",
            "example": "v2.4 - Spring Release"
          },
          "status": {
            "type": "string",
            "enum": [
              "planned",
              "active",
              "completed"
            ],
            "description": "Release lifecycle status."
          },
          "target_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Target release date."
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the release was marked completed."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 creation timestamp."
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "description": "Project this release belongs to."
          }
        }
      },
      "ReleaseDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Release"
          },
          {
            "type": "object",
            "properties": {
              "description": {
                "type": "string",
                "nullable": true,
                "description": "Release description."
              },
              "build_count": {
                "type": "integer",
                "description": "Number of non-deleted builds currently in this release."
              }
            }
          }
        ]
      },
      "CreateReleaseRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "project_key": {
            "type": "string",
            "description": "Project UUID or project_code. Required when using an org-scoped API key; must match a project-scoped key's project."
          },
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Release name. Must be unique in the project (case-insensitive) — duplicates are refused with 409."
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "target_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Target date (today or later)."
          },
          "status": {
            "type": "string",
            "enum": [
              "planned",
              "active",
              "completed"
            ],
            "description": "`planned` (default) or `active`. `completed` is refused: creating an already-completed release is completing one, and completing is only possible in the UI (400 `release_status_not_allowed`)."
          }
        }
      },
      "CreateReleaseResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Release"
          },
          {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "api"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        ]
      },
      "BuildPlacement": {
        "type": "object",
        "description": "A build after a placement. `name` may have gained a \" (2)\" suffix when the target release bucket already had a build with that name — placement never refuses on name collisions.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "v2.4.1 (2)"
          },
          "release_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The release the build now sits in; `null` = the project's \"No release\" bucket."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "planned",
              "active",
              "released"
            ],
            "description": "Build status. CI arrivals are `queued`; an unknown status is stored as `queued`."
          }
        }
      },
      "BuildPlacementRequest": {
        "type": "object",
        "required": [
          "release_id"
        ],
        "description": "Exactly one field is accepted. A completed release refuses the move in either direction (409 `release_completed`).",
        "properties": {
          "release_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Release to place the build into, or `null` to move it back to \"No release\"."
          }
        }
      },
      "BuildListItem": {
        "type": "object",
        "description": "A build as listed by `GET /builds`.",
        "required": [
          "id",
          "name",
          "status",
          "release_id",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "v2.4.1"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "planned",
              "active",
              "released"
            ],
            "description": "Build status. CI arrivals are `queued`; an unknown status is stored as `queued`."
          },
          "release_id": {
            "type": "string",
            "format": "uuid",
            "description": "`null` = the project's \"No release\" bucket.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "CreateTestRunsResponse": {
        "type": "object",
        "description": "Returned when `test_set_ids` with `mode: split_by_set` creates more than one run. There is no `Location` header.",
        "required": [
          "data",
          "source",
          "message"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "display_code"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "display_code": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "api"
            ]
          },
          "message": {
            "type": "string",
            "example": "3 test runs created successfully"
          }
        }
      },
      "TestCaseDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TestCase"
          },
          {
            "type": "object",
            "properties": {
              "steps": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TestStep"
                },
                "nullable": true
              }
            }
          }
        ]
      },
      "Organization": {
        "type": "object",
        "description": "The organization that owns the API key.",
        "required": [
          "id",
          "name",
          "plan",
          "trialDaysLeft",
          "isPaid",
          "isExempt",
          "lockState",
          "isExpired",
          "billing",
          "usage"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "plan": {
            "type": "string",
            "enum": [
              "trial",
              "paid",
              "exempt"
            ]
          },
          "trialDaysLeft": {
            "type": "integer",
            "minimum": 0,
            "description": "Days left in the 90-day preview; 0 once it has ended or the organization is exempt."
          },
          "isExempt": {
            "type": "boolean",
            "description": "Billing exemption overrides trial locks and seat caps without making the organization paid."
          },
          "isPaid": {
            "type": "boolean"
          },
          "lockState": {
            "type": "string",
            "enum": [
              "active",
              "soft_locked",
              "hard_locked",
              "paid"
            ],
            "description": "`hard_locked`: the preview ended, every API call is refused. `soft_locked`: a paid subscription was cancelled, every API call is refused."
          },
          "isExpired": {
            "type": "boolean"
          },
          "billing": {
            "type": "object",
            "properties": {
              "model": {
                "type": "string",
                "enum": [
                  "per_user"
                ]
              },
              "pricePerSeat": {
                "type": "number",
                "example": 25
              },
              "monthlyTotal": {
                "type": "number",
                "description": "0 during the preview."
              },
              "note": {
                "type": "string"
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "currentUsers": {
                "type": "integer"
              },
              "currentProjects": {
                "type": "integer"
              },
              "currentActiveReleases": {
                "type": "integer"
              }
            }
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "build.created.v1",
                "testrun.created.v1",
                "testrun.assigned.v1",
                "testrun.submitted.v1",
                "testrun.completed.v1",
                "testcase.failed.v1",
                "release.released.v1"
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "disabled"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExploratorySessionNote": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "execution_id": {
            "type": "string",
            "format": "uuid",
            "description": "The test_case_executions row this note belongs to."
          },
          "author": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Internal user id of the note author; null if the user was removed."
          },
          "author_name": {
            "type": "string",
            "nullable": true,
            "description": "Display name of the note author."
          },
          "body": {
            "type": "string",
            "description": "The note text."
          },
          "tag": {
            "type": "string",
            "nullable": true,
            "enum": [
              "bug",
              "question",
              "idea",
              "note"
            ],
            "description": "Optional note tag; null = a plain note."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "jira_issue_key": {
            "type": "string",
            "nullable": true,
            "description": "Tracker issue/work-item key if this Bug note was pushed; null otherwise."
          },
          "jira_issue_url": {
            "type": "string",
            "nullable": true
          },
          "pushed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      }
    },
    "headers": {
      "X-RateLimit-Remaining": {
        "description": "Number of API calls remaining in the current billing period.",
        "schema": {
          "type": "integer",
          "example": 987
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp (seconds) when the rate limit counter resets.",
        "schema": {
          "type": "integer",
          "example": 1743465600
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is invalid. The code is `BAD_REQUEST` (a malformed parameter or body value) or `VALIDATION_ERROR` (schema validation; `details.fields` lists each failure, or `details.reason` names the rule).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "BAD_REQUEST",
                "message": "The 'name' field is required.",
                "details": {
                  "field": "name"
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, revoked or expired API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "AUTH_REQUIRED",
                "message": "Missing or invalid Authorization header. Use: Bearer <api_key>"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key lacks the required scope or is scoped to another project, or the organization is locked (preview ended or cancelled subscription: every request is refused).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "FORBIDDEN",
                "message": "API key missing required scope: builds:create"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The requested resource does not exist or is not accessible with this key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RESOURCE_NOT_FOUND",
                "message": "Build not found."
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded. See `X-RateLimit-Reset` header for when the limit resets.",
        "headers": {
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "Rate limit exceeded",
                "details": {
                  "reset_at": 1790400000
                }
              }
            }
          }
        }
      },
      "InternalServerError": {
        "description": "An unexpected server error occurred. Please contact support if this persists.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INTERNAL_ERROR",
                "message": "Internal server error"
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with current state. Branch on `details.reason`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "CONFLICT",
                "message": "This run belongs to a completed release, so its assignment can't be changed",
                "details": {
                  "reason": "release_completed"
                }
              }
            }
          }
        }
      }
    }
  }
}
