diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..ff261ba
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,9 @@
+ARG VARIANT="3.9"
+FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
+
+USER vscode
+
+RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash
+ENV PATH=/home/vscode/.rye/shims:$PATH
+
+RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..c17fdc1
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,43 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/debian
+{
+  "name": "Debian",
+  "build": {
+    "dockerfile": "Dockerfile",
+    "context": ".."
+  },
+
+  "postStartCommand": "rye sync --all-features",
+
+  "customizations": {
+    "vscode": {
+      "extensions": [
+        "ms-python.python"
+      ],
+      "settings": { 
+        "terminal.integrated.shell.linux": "/bin/bash",
+        "python.pythonPath": ".venv/bin/python",
+        "python.defaultInterpreterPath": ".venv/bin/python",
+        "python.typeChecking": "basic",
+        "terminal.integrated.env.linux": {
+          "PATH": "/home/vscode/.rye/shims:${env:PATH}"
+        }
+      }
+    }
+  },
+  "features": {
+    "ghcr.io/devcontainers/features/node:1": {}
+  }
+
+  // Features to add to the dev container. More info: https://containers.dev/features.
+  // "features": {},
+
+  // Use 'forwardPorts' to make a list of ports inside the container available locally.
+  // "forwardPorts": [],
+
+  // Configure tool-specific properties.
+  // "customizations": {},
+
+  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+  // "remoteUser": "root"
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..322b0f7
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,96 @@
+name: CI
+on:
+  push:
+    branches-ignore:
+      - 'generated'
+      - 'codegen/**'
+      - 'integrated/**'
+      - 'stl-preview-head/**'
+      - 'stl-preview-base/**'
+  pull_request:
+    branches-ignore:
+      - 'stl-preview-head/**'
+      - 'stl-preview-base/**'
+
+jobs:
+  lint:
+    timeout-minutes: 10
+    name: lint
+    runs-on: ${{ github.repository == 'stainless-sdks/gitpod-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.44.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Install dependencies
+        run: rye sync --all-features
+
+      - name: Run lints
+        run: ./scripts/lint
+
+  build:
+    if: github.repository == 'stainless-sdks/gitpod-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
+    timeout-minutes: 10
+    name: build
+    permissions:
+      contents: read
+      id-token: write
+    runs-on: depot-ubuntu-24.04
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.44.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Install dependencies
+        run: rye sync --all-features
+
+      - name: Run build
+        run: rye build
+
+      - name: Get GitHub OIDC Token
+        id: github-oidc
+        uses: actions/github-script@v6
+        with:
+          script: core.setOutput('github_token', await core.getIDToken());
+
+      - name: Upload tarball
+        env:
+          URL: https://pkg.stainless.com/s
+          AUTH: ${{ steps.github-oidc.outputs.github_token }}
+          SHA: ${{ github.sha }}
+        run: ./scripts/utils/upload-artifact.sh
+
+  test:
+    timeout-minutes: 10
+    name: test
+    runs-on: ${{ github.repository == 'stainless-sdks/gitpod-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.44.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Bootstrap
+        run: ./scripts/bootstrap
+
+      - name: Run tests
+        run: ./scripts/test
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
new file mode 100644
index 0000000..6cd33a6
--- /dev/null
+++ b/.github/workflows/publish-pypi.yml
@@ -0,0 +1,31 @@
+# This workflow is triggered when a GitHub release is created.
+# It can also be run manually to re-publish to PyPI in case it failed for some reason.
+# You can run this workflow by navigating to https://www.github.com/gitpod-io/gitpod-sdk-python/actions/workflows/publish-pypi.yml
+name: Publish PyPI
+on:
+  workflow_dispatch:
+
+  release:
+    types: [published]
+
+jobs:
+  publish:
+    name: publish
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.44.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Publish to PyPI
+        run: |
+          bash ./bin/publish-pypi
+        env:
+          PYPI_TOKEN: ${{ secrets.GITPOD_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
new file mode 100644
index 0000000..f7fa281
--- /dev/null
+++ b/.github/workflows/release-doctor.yml
@@ -0,0 +1,21 @@
+name: Release Doctor
+on:
+  pull_request:
+    branches:
+      - main
+  workflow_dispatch:
+
+jobs:
+  release_doctor:
+    name: release doctor
+    runs-on: ubuntu-latest
+    if: github.repository == 'gitpod-io/gitpod-sdk-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Check release environment
+        run: |
+          bash ./bin/check-release-environment
+        env:
+          PYPI_TOKEN: ${{ secrets.GITPOD_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8779740
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+.prism.log
+.vscode
+_dev
+
+__pycache__
+.mypy_cache
+
+dist
+
+.venv
+.idea
+
+.env
+.envrc
+codegen.log
+Brewfile.lock.json
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..43077b2
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.9.18
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 0000000..6b7b74c
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+  ".": "0.3.0"
+}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
new file mode 100644
index 0000000..d375176
--- /dev/null
+++ b/.stats.yml
@@ -0,0 +1,4 @@
+configured_endpoints: 119
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gitpod%2Fgitpod-ca9a49ac7fbb63f55611fd7cd48a22a3ff8b38a797125c8513e891d9b7345550.yml
+openapi_spec_hash: fd6ffbdfaefcc555e61ca1c565e05214
+config_hash: bb9d0a0bdadbee0985dd7c1e4f0e9e8a
diff --git a/Brewfile b/Brewfile
new file mode 100644
index 0000000..492ca37
--- /dev/null
+++ b/Brewfile
@@ -0,0 +1,2 @@
+brew "rye"
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..8e1d17a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,128 @@
+## Setting up the environment
+
+### With Rye
+
+We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run:
+
+```sh
+$ ./scripts/bootstrap
+```
+
+Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run:
+
+```sh
+$ rye sync --all-features
+```
+
+You can then run scripts using `rye run python script.py` or by activating the virtual environment:
+
+```sh
+# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work
+$ source .venv/bin/activate
+
+# now you can omit the `rye run` prefix
+$ python script.py
+```
+
+### Without Rye
+
+Alternatively if you don't want to install `Rye`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command:
+
+```sh
+$ pip install -r requirements-dev.lock
+```
+
+## Modifying/Adding code
+
+Most of the SDK is generated code. Modifications to code will be persisted between generations, but may
+result in merge conflicts between manual patches and changes from the generator. The generator will never
+modify the contents of the `src/gitpod/lib/` and `examples/` directories.
+
+## Adding and running examples
+
+All files in the `examples/` directory are not modified by the generator and can be freely edited or added to.
+
+```py
+# add an example to examples/<your-example>.py
+
+#!/usr/bin/env -S rye run python
+…
+```
+
+```sh
+$ chmod +x examples/<your-example>.py
+# run the example against your api
+$ ./examples/<your-example>.py
+```
+
+## Using the repository from source
+
+If you’d like to use the repository from source, you can either install from git or link to a cloned repository:
+
+To install via git:
+
+```sh
+$ pip install git+ssh://git@github.com/gitpod-io/gitpod-sdk-python.git
+```
+
+Alternatively, you can build from source and install the wheel file:
+
+Building this package will create two files in the `dist/` directory, a `.tar.gz` containing the source files and a `.whl` that can be used to install the package efficiently.
+
+To create a distributable version of the library, all you have to do is run this command:
+
+```sh
+$ rye build
+# or
+$ python -m build
+```
+
+Then to install:
+
+```sh
+$ pip install ./path-to-wheel-file.whl
+```
+
+## Running tests
+
+Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
+
+```sh
+# you will need npm installed
+$ npx prism mock path/to/your/openapi.yml
+```
+
+```sh
+$ ./scripts/test
+```
+
+## Linting and formatting
+
+This repository uses [ruff](https://github.com/astral-sh/ruff) and
+[black](https://github.com/psf/black) to format the code in the repository.
+
+To lint:
+
+```sh
+$ ./scripts/lint
+```
+
+To format and fix all ruff issues automatically:
+
+```sh
+$ ./scripts/format
+```
+
+## Publishing and releases
+
+Changes made to this repository via the automated release PR pipeline should publish to PyPI automatically. If
+the changes aren't made through the automated pipeline, you may want to make releases manually.
+
+### Publish with a GitHub workflow
+
+You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/gitpod-io/gitpod-sdk-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.
+
+### Publish manually
+
+If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on
+the environment.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..01fd4f2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2025 Gitpod
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
index c2b730a..b5d5626 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,441 @@
-# gitpod-python
\ No newline at end of file
+# Gitpod Python API library
+
+<!-- prettier-ignore -->
+[![PyPI version](https://img.shields.io/pypi/v/gitpod-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/gitpod-sdk/)
+
+The Gitpod Python library provides convenient access to the Gitpod REST API from any Python 3.8+
+application. The library includes type definitions for all request params and response fields,
+and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
+
+It is generated with [Stainless](https://www.stainless.com/).
+
+## Documentation
+
+The REST API documentation can be found on [docs.gitpod.io](https://docs.gitpod.io). The full API of this library can be found in [api.md](api.md).
+
+## Installation
+
+```sh
+# install from PyPI
+pip install gitpod-sdk
+```
+
+## Usage
+
+The full API of this library can be found in [api.md](api.md).
+
+```python
+import os
+from gitpod import Gitpod
+
+client = Gitpod(
+    bearer_token=os.environ.get("GITPOD_API_KEY"),  # This is the default and can be omitted
+)
+
+response = client.identity.get_authenticated_identity()
+print(response.organization_id)
+```
+
+While you can provide a `bearer_token` keyword argument,
+we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
+to add `GITPOD_API_KEY="My Bearer Token"` to your `.env` file
+so that your Bearer Token is not stored in source control.
+
+## Async usage
+
+Simply import `AsyncGitpod` instead of `Gitpod` and use `await` with each API call:
+
+```python
+import os
+import asyncio
+from gitpod import AsyncGitpod
+
+client = AsyncGitpod(
+    bearer_token=os.environ.get("GITPOD_API_KEY"),  # This is the default and can be omitted
+)
+
+
+async def main() -> None:
+    response = await client.identity.get_authenticated_identity()
+    print(response.organization_id)
+
+
+asyncio.run(main())
+```
+
+Functionality between the synchronous and asynchronous clients is otherwise identical.
+
+### With aiohttp
+
+By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
+
+You can enable this by installing `aiohttp`:
+
+```sh
+# install from PyPI
+pip install gitpod-sdk[aiohttp]
+```
+
+Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
+
+```python
+import asyncio
+from gitpod import DefaultAioHttpClient
+from gitpod import AsyncGitpod
+
+
+async def main() -> None:
+    async with AsyncGitpod(
+        bearer_token="My Bearer Token",
+        http_client=DefaultAioHttpClient(),
+    ) as client:
+        response = await client.identity.get_authenticated_identity()
+        print(response.organization_id)
+
+
+asyncio.run(main())
+```
+
+## Using types
+
+Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
+
+- Serializing back into JSON, `model.to_json()`
+- Converting to a dictionary, `model.to_dict()`
+
+Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
+
+## Pagination
+
+List methods in the Gitpod API are paginated.
+
+This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
+
+```python
+from gitpod import Gitpod
+
+client = Gitpod()
+
+all_environments = []
+# Automatically fetches more pages as needed.
+for environment in client.environments.list():
+    # Do something with environment here
+    all_environments.append(environment)
+print(all_environments)
+```
+
+Or, asynchronously:
+
+```python
+import asyncio
+from gitpod import AsyncGitpod
+
+client = AsyncGitpod()
+
+
+async def main() -> None:
+    all_environments = []
+    # Iterate through items across all pages, issuing requests as needed.
+    async for environment in client.environments.list():
+        all_environments.append(environment)
+    print(all_environments)
+
+
+asyncio.run(main())
+```
+
+Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
+
+```python
+first_page = await client.environments.list()
+if first_page.has_next_page():
+    print(f"will fetch next page using these details: {first_page.next_page_info()}")
+    next_page = await first_page.get_next_page()
+    print(f"number of items we just fetched: {len(next_page.environments)}")
+
+# Remove `await` for non-async usage.
+```
+
+Or just work directly with the returned data:
+
+```python
+first_page = await client.environments.list()
+
+print(f"next page cursor: {first_page.pagination.next_token}")  # => "next page cursor: ..."
+for environment in first_page.environments:
+    print(environment.id)
+
+# Remove `await` for non-async usage.
+```
+
+## Nested params
+
+Nested parameters are dictionaries, typed using `TypedDict`, for example:
+
+```python
+from gitpod import Gitpod
+
+client = Gitpod()
+
+page = client.accounts.list_login_providers(
+    filter={},
+)
+print(page.login_providers)
+```
+
+## Handling errors
+
+When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `gitpod.APIConnectionError` is raised.
+
+When the API returns a non-success status code (that is, 4xx or 5xx
+response), a subclass of `gitpod.APIStatusError` is raised, containing `status_code` and `response` properties.
+
+All errors inherit from `gitpod.APIError`.
+
+```python
+import gitpod
+from gitpod import Gitpod
+
+client = Gitpod()
+
+try:
+    client.identity.get_authenticated_identity()
+except gitpod.APIConnectionError as e:
+    print("The server could not be reached")
+    print(e.__cause__)  # an underlying Exception, likely raised within httpx.
+except gitpod.RateLimitError as e:
+    print("A 429 status code was received; we should back off a bit.")
+except gitpod.APIStatusError as e:
+    print("Another non-200-range status code was received")
+    print(e.status_code)
+    print(e.response)
+```
+
+Error codes are as follows:
+
+| Status Code | Error Type                 |
+| ----------- | -------------------------- |
+| 400         | `BadRequestError`          |
+| 401         | `AuthenticationError`      |
+| 403         | `PermissionDeniedError`    |
+| 404         | `NotFoundError`            |
+| 422         | `UnprocessableEntityError` |
+| 429         | `RateLimitError`           |
+| >=500       | `InternalServerError`      |
+| N/A         | `APIConnectionError`       |
+
+### Retries
+
+Certain errors are automatically retried 2 times by default, with a short exponential backoff.
+Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
+429 Rate Limit, and >=500 Internal errors are all retried by default.
+
+You can use the `max_retries` option to configure or disable retry settings:
+
+```python
+from gitpod import Gitpod
+
+# Configure the default for all requests:
+client = Gitpod(
+    # default is 2
+    max_retries=0,
+)
+
+# Or, configure per-request:
+client.with_options(max_retries=5).identity.get_authenticated_identity()
+```
+
+### Timeouts
+
+By default requests time out after 1 minute. You can configure this with a `timeout` option,
+which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
+
+```python
+from gitpod import Gitpod
+
+# Configure the default for all requests:
+client = Gitpod(
+    # 20 seconds (default is 1 minute)
+    timeout=20.0,
+)
+
+# More granular control:
+client = Gitpod(
+    timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
+)
+
+# Override per-request:
+client.with_options(timeout=5.0).identity.get_authenticated_identity()
+```
+
+On timeout, an `APITimeoutError` is thrown.
+
+Note that requests that time out are [retried twice by default](#retries).
+
+## Advanced
+
+### Logging
+
+We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
+
+You can enable logging by setting the environment variable `GITPOD_LOG` to `info`.
+
+```shell
+$ export GITPOD_LOG=info
+```
+
+Or to `debug` for more verbose logging.
+
+### How to tell whether `None` means `null` or missing
+
+In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`:
+
+```py
+if response.my_field is None:
+  if 'my_field' not in response.model_fields_set:
+    print('Got json like {}, without a "my_field" key present at all.')
+  else:
+    print('Got json like {"my_field": null}.')
+```
+
+### Accessing raw response data (e.g. headers)
+
+The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
+
+```py
+from gitpod import Gitpod
+
+client = Gitpod()
+response = client.identity.with_raw_response.get_authenticated_identity()
+print(response.headers.get('X-My-Header'))
+
+identity = response.parse()  # get the object that `identity.get_authenticated_identity()` would have returned
+print(identity.organization_id)
+```
+
+These methods return an [`APIResponse`](https://github.com/gitpod-io/gitpod-sdk-python/tree/main/src/gitpod/_response.py) object.
+
+The async client returns an [`AsyncAPIResponse`](https://github.com/gitpod-io/gitpod-sdk-python/tree/main/src/gitpod/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
+
+#### `.with_streaming_response`
+
+The above interface eagerly reads the full response body when you make the request, which may not always be what you want.
+
+To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.
+
+```python
+with client.identity.with_streaming_response.get_authenticated_identity() as response:
+    print(response.headers.get("X-My-Header"))
+
+    for line in response.iter_lines():
+        print(line)
+```
+
+The context manager is required so that the response will reliably be closed.
+
+### Making custom/undocumented requests
+
+This library is typed for convenient access to the documented API.
+
+If you need to access undocumented endpoints, params, or response properties, the library can still be used.
+
+#### Undocumented endpoints
+
+To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other
+http verbs. Options on the client will be respected (such as retries) when making this request.
+
+```py
+import httpx
+
+response = client.post(
+    "/foo",
+    cast_to=httpx.Response,
+    body={"my_param": True},
+)
+
+print(response.headers.get("x-foo"))
+```
+
+#### Undocumented request params
+
+If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request
+options.
+
+#### Undocumented response properties
+
+To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You
+can also get all the extra fields on the Pydantic model as a dict with
+[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra).
+
+### Configuring the HTTP client
+
+You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including:
+
+- Support for [proxies](https://www.python-httpx.org/advanced/proxies/)
+- Custom [transports](https://www.python-httpx.org/advanced/transports/)
+- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality
+
+```python
+import httpx
+from gitpod import Gitpod, DefaultHttpxClient
+
+client = Gitpod(
+    # Or use the `GITPOD_BASE_URL` env var
+    base_url="http://my.test.server.example.com:8083",
+    http_client=DefaultHttpxClient(
+        proxy="http://my.test.proxy.example.com",
+        transport=httpx.HTTPTransport(local_address="0.0.0.0"),
+    ),
+)
+```
+
+You can also customize the client on a per-request basis by using `with_options()`:
+
+```python
+client.with_options(http_client=DefaultHttpxClient(...))
+```
+
+### Managing HTTP resources
+
+By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting.
+
+```py
+from gitpod import Gitpod
+
+with Gitpod() as client:
+  # make requests here
+  ...
+
+# HTTP client is now closed
+```
+
+## Versioning
+
+This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
+
+1. Changes that only affect static types, without breaking runtime behavior.
+2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
+3. Changes that we do not expect to impact the vast majority of users in practice.
+
+We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
+
+We are keen for your feedback; please open an [issue](https://www.github.com/gitpod-io/gitpod-sdk-python/issues) with questions, bugs, or suggestions.
+
+### Determining the installed version
+
+If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version.
+
+You can determine the version that is being used at runtime with:
+
+```py
+import gitpod
+print(gitpod.__version__)
+```
+
+## Requirements
+
+Python 3.8 or higher.
+
+## Contributing
+
+See [the contributing documentation](./CONTRIBUTING.md).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..efd9088
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,27 @@
+# Security Policy
+
+## Reporting Security Issues
+
+This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
+
+To report a security issue, please contact the Stainless team at security@stainless.com.
+
+## Responsible Disclosure
+
+We appreciate the efforts of security researchers and individuals who help us maintain the security of
+SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible
+disclosure practices by allowing us a reasonable amount of time to investigate and address the issue
+before making any information public.
+
+## Reporting Non-SDK Related Security Issues
+
+If you encounter security issues that are not directly related to SDKs but pertain to the services
+or products provided by Gitpod, please follow the respective company's security reporting guidelines.
+
+### Gitpod Terms and Policies
+
+Please contact dev-feedback@gitpod.com for any questions or concerns regarding the security of our services.
+
+---
+
+Thank you for helping us keep the SDKs and systems they interact with secure.
diff --git a/api.md b/api.md
new file mode 100644
index 0000000..d6b34b7
--- /dev/null
+++ b/api.md
@@ -0,0 +1,591 @@
+# Shared Types
+
+```python
+from gitpod.types import (
+    AutomationTrigger,
+    EnvironmentClass,
+    ErrorCode,
+    FieldValue,
+    Gateway,
+    OrganizationRole,
+    Principal,
+    RunsOn,
+    Subject,
+    Task,
+    TaskExecution,
+    TaskExecutionMetadata,
+    TaskExecutionPhase,
+    TaskExecutionSpec,
+    TaskExecutionStatus,
+    TaskMetadata,
+    TaskSpec,
+    UserStatus,
+)
+```
+
+# Accounts
+
+Types:
+
+```python
+from gitpod.types import (
+    Account,
+    AccountMembership,
+    JoinableOrganization,
+    LoginProvider,
+    AccountRetrieveResponse,
+    AccountGetSSOLoginURLResponse,
+    AccountListJoinableOrganizationsResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.AccountService/GetAccount">client.accounts.<a href="./src/gitpod/resources/accounts.py">retrieve</a>(\*\*<a href="src/gitpod/types/account_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/account_retrieve_response.py">AccountRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.AccountService/DeleteAccount">client.accounts.<a href="./src/gitpod/resources/accounts.py">delete</a>(\*\*<a href="src/gitpod/types/account_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.AccountService/GetSSOLoginURL">client.accounts.<a href="./src/gitpod/resources/accounts.py">get_sso_login_url</a>(\*\*<a href="src/gitpod/types/account_get_sso_login_url_params.py">params</a>) -> <a href="./src/gitpod/types/account_get_sso_login_url_response.py">AccountGetSSOLoginURLResponse</a></code>
+- <code title="post /gitpod.v1.AccountService/ListJoinableOrganizations">client.accounts.<a href="./src/gitpod/resources/accounts.py">list_joinable_organizations</a>(\*\*<a href="src/gitpod/types/account_list_joinable_organizations_params.py">params</a>) -> <a href="./src/gitpod/types/account_list_joinable_organizations_response.py">AccountListJoinableOrganizationsResponse</a></code>
+- <code title="post /gitpod.v1.AccountService/ListLoginProviders">client.accounts.<a href="./src/gitpod/resources/accounts.py">list_login_providers</a>(\*\*<a href="src/gitpod/types/account_list_login_providers_params.py">params</a>) -> <a href="./src/gitpod/types/login_provider.py">SyncLoginProvidersPage[LoginProvider]</a></code>
+
+# Editors
+
+Types:
+
+```python
+from gitpod.types import Editor, EditorRetrieveResponse, EditorResolveURLResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.EditorService/GetEditor">client.editors.<a href="./src/gitpod/resources/editors.py">retrieve</a>(\*\*<a href="src/gitpod/types/editor_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/editor_retrieve_response.py">EditorRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.EditorService/ListEditors">client.editors.<a href="./src/gitpod/resources/editors.py">list</a>(\*\*<a href="src/gitpod/types/editor_list_params.py">params</a>) -> <a href="./src/gitpod/types/editor.py">SyncEditorsPage[Editor]</a></code>
+- <code title="post /gitpod.v1.EditorService/ResolveEditorURL">client.editors.<a href="./src/gitpod/resources/editors.py">resolve_url</a>(\*\*<a href="src/gitpod/types/editor_resolve_url_params.py">params</a>) -> <a href="./src/gitpod/types/editor_resolve_url_response.py">EditorResolveURLResponse</a></code>
+
+# Environments
+
+Types:
+
+```python
+from gitpod.types import (
+    AdmissionLevel,
+    Environment,
+    EnvironmentActivitySignal,
+    EnvironmentMetadata,
+    EnvironmentPhase,
+    EnvironmentSpec,
+    EnvironmentStatus,
+    EnvironmentCreateResponse,
+    EnvironmentRetrieveResponse,
+    EnvironmentCreateEnvironmentTokenResponse,
+    EnvironmentCreateFromProjectResponse,
+    EnvironmentCreateLogsTokenResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.EnvironmentService/CreateEnvironment">client.environments.<a href="./src/gitpod/resources/environments/environments.py">create</a>(\*\*<a href="src/gitpod/types/environment_create_params.py">params</a>) -> <a href="./src/gitpod/types/environment_create_response.py">EnvironmentCreateResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentService/GetEnvironment">client.environments.<a href="./src/gitpod/resources/environments/environments.py">retrieve</a>(\*\*<a href="src/gitpod/types/environment_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/environment_retrieve_response.py">EnvironmentRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentService/UpdateEnvironment">client.environments.<a href="./src/gitpod/resources/environments/environments.py">update</a>(\*\*<a href="src/gitpod/types/environment_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentService/ListEnvironments">client.environments.<a href="./src/gitpod/resources/environments/environments.py">list</a>(\*\*<a href="src/gitpod/types/environment_list_params.py">params</a>) -> <a href="./src/gitpod/types/environment.py">SyncEnvironmentsPage[Environment]</a></code>
+- <code title="post /gitpod.v1.EnvironmentService/DeleteEnvironment">client.environments.<a href="./src/gitpod/resources/environments/environments.py">delete</a>(\*\*<a href="src/gitpod/types/environment_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentService/CreateEnvironmentAccessToken">client.environments.<a href="./src/gitpod/resources/environments/environments.py">create_environment_token</a>(\*\*<a href="src/gitpod/types/environment_create_environment_token_params.py">params</a>) -> <a href="./src/gitpod/types/environment_create_environment_token_response.py">EnvironmentCreateEnvironmentTokenResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentService/CreateEnvironmentFromProject">client.environments.<a href="./src/gitpod/resources/environments/environments.py">create_from_project</a>(\*\*<a href="src/gitpod/types/environment_create_from_project_params.py">params</a>) -> <a href="./src/gitpod/types/environment_create_from_project_response.py">EnvironmentCreateFromProjectResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentService/CreateEnvironmentLogsToken">client.environments.<a href="./src/gitpod/resources/environments/environments.py">create_logs_token</a>(\*\*<a href="src/gitpod/types/environment_create_logs_token_params.py">params</a>) -> <a href="./src/gitpod/types/environment_create_logs_token_response.py">EnvironmentCreateLogsTokenResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentService/MarkEnvironmentActive">client.environments.<a href="./src/gitpod/resources/environments/environments.py">mark_active</a>(\*\*<a href="src/gitpod/types/environment_mark_active_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentService/StartEnvironment">client.environments.<a href="./src/gitpod/resources/environments/environments.py">start</a>(\*\*<a href="src/gitpod/types/environment_start_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentService/StopEnvironment">client.environments.<a href="./src/gitpod/resources/environments/environments.py">stop</a>(\*\*<a href="src/gitpod/types/environment_stop_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentService/UnarchiveEnvironment">client.environments.<a href="./src/gitpod/resources/environments/environments.py">unarchive</a>(\*\*<a href="src/gitpod/types/environment_unarchive_params.py">params</a>) -> object</code>
+
+## Automations
+
+Types:
+
+```python
+from gitpod.types.environments import AutomationsFile, AutomationUpsertResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.EnvironmentAutomationService/UpsertAutomationsFile">client.environments.automations.<a href="./src/gitpod/resources/environments/automations/automations.py">upsert</a>(\*\*<a href="src/gitpod/types/environments/automation_upsert_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automation_upsert_response.py">AutomationUpsertResponse</a></code>
+
+### Services
+
+Types:
+
+```python
+from gitpod.types.environments.automations import (
+    Service,
+    ServiceMetadata,
+    ServicePhase,
+    ServiceSpec,
+    ServiceStatus,
+    ServiceCreateResponse,
+    ServiceRetrieveResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.EnvironmentAutomationService/CreateService">client.environments.automations.services.<a href="./src/gitpod/resources/environments/automations/services.py">create</a>(\*\*<a href="src/gitpod/types/environments/automations/service_create_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automations/service_create_response.py">ServiceCreateResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/GetService">client.environments.automations.services.<a href="./src/gitpod/resources/environments/automations/services.py">retrieve</a>(\*\*<a href="src/gitpod/types/environments/automations/service_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automations/service_retrieve_response.py">ServiceRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/UpdateService">client.environments.automations.services.<a href="./src/gitpod/resources/environments/automations/services.py">update</a>(\*\*<a href="src/gitpod/types/environments/automations/service_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/ListServices">client.environments.automations.services.<a href="./src/gitpod/resources/environments/automations/services.py">list</a>(\*\*<a href="src/gitpod/types/environments/automations/service_list_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automations/service.py">SyncServicesPage[Service]</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/DeleteService">client.environments.automations.services.<a href="./src/gitpod/resources/environments/automations/services.py">delete</a>(\*\*<a href="src/gitpod/types/environments/automations/service_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/StartService">client.environments.automations.services.<a href="./src/gitpod/resources/environments/automations/services.py">start</a>(\*\*<a href="src/gitpod/types/environments/automations/service_start_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/StopService">client.environments.automations.services.<a href="./src/gitpod/resources/environments/automations/services.py">stop</a>(\*\*<a href="src/gitpod/types/environments/automations/service_stop_params.py">params</a>) -> object</code>
+
+### Tasks
+
+Types:
+
+```python
+from gitpod.types.environments.automations import (
+    TaskCreateResponse,
+    TaskRetrieveResponse,
+    TaskStartResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.EnvironmentAutomationService/CreateTask">client.environments.automations.tasks.<a href="./src/gitpod/resources/environments/automations/tasks/tasks.py">create</a>(\*\*<a href="src/gitpod/types/environments/automations/task_create_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automations/task_create_response.py">TaskCreateResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/GetTask">client.environments.automations.tasks.<a href="./src/gitpod/resources/environments/automations/tasks/tasks.py">retrieve</a>(\*\*<a href="src/gitpod/types/environments/automations/task_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automations/task_retrieve_response.py">TaskRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/UpdateTask">client.environments.automations.tasks.<a href="./src/gitpod/resources/environments/automations/tasks/tasks.py">update</a>(\*\*<a href="src/gitpod/types/environments/automations/task_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/ListTasks">client.environments.automations.tasks.<a href="./src/gitpod/resources/environments/automations/tasks/tasks.py">list</a>(\*\*<a href="src/gitpod/types/environments/automations/task_list_params.py">params</a>) -> <a href="./src/gitpod/types/shared/task.py">SyncTasksPage[Task]</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/DeleteTask">client.environments.automations.tasks.<a href="./src/gitpod/resources/environments/automations/tasks/tasks.py">delete</a>(\*\*<a href="src/gitpod/types/environments/automations/task_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/StartTask">client.environments.automations.tasks.<a href="./src/gitpod/resources/environments/automations/tasks/tasks.py">start</a>(\*\*<a href="src/gitpod/types/environments/automations/task_start_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automations/task_start_response.py">TaskStartResponse</a></code>
+
+#### Executions
+
+Types:
+
+```python
+from gitpod.types.environments.automations.tasks import ExecutionRetrieveResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.EnvironmentAutomationService/GetTaskExecution">client.environments.automations.tasks.executions.<a href="./src/gitpod/resources/environments/automations/tasks/executions.py">retrieve</a>(\*\*<a href="src/gitpod/types/environments/automations/tasks/execution_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/environments/automations/tasks/execution_retrieve_response.py">ExecutionRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/ListTaskExecutions">client.environments.automations.tasks.executions.<a href="./src/gitpod/resources/environments/automations/tasks/executions.py">list</a>(\*\*<a href="src/gitpod/types/environments/automations/tasks/execution_list_params.py">params</a>) -> <a href="./src/gitpod/types/shared/task_execution.py">SyncTaskExecutionsPage[TaskExecution]</a></code>
+- <code title="post /gitpod.v1.EnvironmentAutomationService/StopTaskExecution">client.environments.automations.tasks.executions.<a href="./src/gitpod/resources/environments/automations/tasks/executions.py">stop</a>(\*\*<a href="src/gitpod/types/environments/automations/tasks/execution_stop_params.py">params</a>) -> object</code>
+
+## Classes
+
+Methods:
+
+- <code title="post /gitpod.v1.EnvironmentService/ListEnvironmentClasses">client.environments.classes.<a href="./src/gitpod/resources/environments/classes.py">list</a>(\*\*<a href="src/gitpod/types/environments/class_list_params.py">params</a>) -> <a href="./src/gitpod/types/shared/environment_class.py">SyncEnvironmentClassesPage[EnvironmentClass]</a></code>
+
+# Events
+
+Types:
+
+```python
+from gitpod.types import ResourceOperation, ResourceType, EventListResponse, EventWatchResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.EventService/ListAuditLogs">client.events.<a href="./src/gitpod/resources/events.py">list</a>(\*\*<a href="src/gitpod/types/event_list_params.py">params</a>) -> <a href="./src/gitpod/types/event_list_response.py">SyncEntriesPage[EventListResponse]</a></code>
+- <code title="post /gitpod.v1.EventService/WatchEvents">client.events.<a href="./src/gitpod/resources/events.py">watch</a>(\*\*<a href="src/gitpod/types/event_watch_params.py">params</a>) -> <a href="./src/gitpod/types/event_watch_response.py">JSONLDecoder[EventWatchResponse]</a></code>
+
+# Gateways
+
+Methods:
+
+- <code title="post /gitpod.v1.GatewayService/ListGateways">client.gateways.<a href="./src/gitpod/resources/gateways.py">list</a>(\*\*<a href="src/gitpod/types/gateway_list_params.py">params</a>) -> <a href="./src/gitpod/types/shared/gateway.py">SyncGatewaysPage[Gateway]</a></code>
+
+# Groups
+
+Types:
+
+```python
+from gitpod.types import Group
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.GroupService/ListGroups">client.groups.<a href="./src/gitpod/resources/groups.py">list</a>(\*\*<a href="src/gitpod/types/group_list_params.py">params</a>) -> <a href="./src/gitpod/types/group.py">SyncGroupsPage[Group]</a></code>
+
+# Identity
+
+Types:
+
+```python
+from gitpod.types import (
+    IDTokenVersion,
+    IdentityExchangeTokenResponse,
+    IdentityGetAuthenticatedIdentityResponse,
+    IdentityGetIDTokenResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.IdentityService/ExchangeToken">client.identity.<a href="./src/gitpod/resources/identity.py">exchange_token</a>(\*\*<a href="src/gitpod/types/identity_exchange_token_params.py">params</a>) -> <a href="./src/gitpod/types/identity_exchange_token_response.py">IdentityExchangeTokenResponse</a></code>
+- <code title="post /gitpod.v1.IdentityService/GetAuthenticatedIdentity">client.identity.<a href="./src/gitpod/resources/identity.py">get_authenticated_identity</a>(\*\*<a href="src/gitpod/types/identity_get_authenticated_identity_params.py">params</a>) -> <a href="./src/gitpod/types/identity_get_authenticated_identity_response.py">IdentityGetAuthenticatedIdentityResponse</a></code>
+- <code title="post /gitpod.v1.IdentityService/GetIDToken">client.identity.<a href="./src/gitpod/resources/identity.py">get_id_token</a>(\*\*<a href="src/gitpod/types/identity_get_id_token_params.py">params</a>) -> <a href="./src/gitpod/types/identity_get_id_token_response.py">IdentityGetIDTokenResponse</a></code>
+
+# Organizations
+
+Types:
+
+```python
+from gitpod.types import (
+    InviteDomains,
+    Organization,
+    OrganizationMember,
+    OrganizationTier,
+    OrganizationCreateResponse,
+    OrganizationRetrieveResponse,
+    OrganizationUpdateResponse,
+    OrganizationJoinResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.OrganizationService/CreateOrganization">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">create</a>(\*\*<a href="src/gitpod/types/organization_create_params.py">params</a>) -> <a href="./src/gitpod/types/organization_create_response.py">OrganizationCreateResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/GetOrganization">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">retrieve</a>(\*\*<a href="src/gitpod/types/organization_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/organization_retrieve_response.py">OrganizationRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/UpdateOrganization">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">update</a>(\*\*<a href="src/gitpod/types/organization_update_params.py">params</a>) -> <a href="./src/gitpod/types/organization_update_response.py">OrganizationUpdateResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/DeleteOrganization">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">delete</a>(\*\*<a href="src/gitpod/types/organization_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.OrganizationService/JoinOrganization">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">join</a>(\*\*<a href="src/gitpod/types/organization_join_params.py">params</a>) -> <a href="./src/gitpod/types/organization_join_response.py">OrganizationJoinResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/LeaveOrganization">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">leave</a>(\*\*<a href="src/gitpod/types/organization_leave_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.OrganizationService/ListMembers">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">list_members</a>(\*\*<a href="src/gitpod/types/organization_list_members_params.py">params</a>) -> <a href="./src/gitpod/types/organization_member.py">SyncMembersPage[OrganizationMember]</a></code>
+- <code title="post /gitpod.v1.OrganizationService/SetRole">client.organizations.<a href="./src/gitpod/resources/organizations/organizations.py">set_role</a>(\*\*<a href="src/gitpod/types/organization_set_role_params.py">params</a>) -> object</code>
+
+## DomainVerifications
+
+Types:
+
+```python
+from gitpod.types.organizations import (
+    DomainVerification,
+    DomainVerificationState,
+    DomainVerificationCreateResponse,
+    DomainVerificationRetrieveResponse,
+    DomainVerificationVerifyResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.OrganizationService/CreateDomainVerification">client.organizations.domain_verifications.<a href="./src/gitpod/resources/organizations/domain_verifications.py">create</a>(\*\*<a href="src/gitpod/types/organizations/domain_verification_create_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/domain_verification_create_response.py">DomainVerificationCreateResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/GetDomainVerification">client.organizations.domain_verifications.<a href="./src/gitpod/resources/organizations/domain_verifications.py">retrieve</a>(\*\*<a href="src/gitpod/types/organizations/domain_verification_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/domain_verification_retrieve_response.py">DomainVerificationRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/ListDomainVerifications">client.organizations.domain_verifications.<a href="./src/gitpod/resources/organizations/domain_verifications.py">list</a>(\*\*<a href="src/gitpod/types/organizations/domain_verification_list_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/domain_verification.py">SyncDomainVerificationsPage[DomainVerification]</a></code>
+- <code title="post /gitpod.v1.OrganizationService/DeleteDomainVerification">client.organizations.domain_verifications.<a href="./src/gitpod/resources/organizations/domain_verifications.py">delete</a>(\*\*<a href="src/gitpod/types/organizations/domain_verification_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.OrganizationService/VerifyDomain">client.organizations.domain_verifications.<a href="./src/gitpod/resources/organizations/domain_verifications.py">verify</a>(\*\*<a href="src/gitpod/types/organizations/domain_verification_verify_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/domain_verification_verify_response.py">DomainVerificationVerifyResponse</a></code>
+
+## Invites
+
+Types:
+
+```python
+from gitpod.types.organizations import (
+    OrganizationInvite,
+    InviteCreateResponse,
+    InviteRetrieveResponse,
+    InviteGetSummaryResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.OrganizationService/CreateOrganizationInvite">client.organizations.invites.<a href="./src/gitpod/resources/organizations/invites.py">create</a>(\*\*<a href="src/gitpod/types/organizations/invite_create_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/invite_create_response.py">InviteCreateResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/GetOrganizationInvite">client.organizations.invites.<a href="./src/gitpod/resources/organizations/invites.py">retrieve</a>(\*\*<a href="src/gitpod/types/organizations/invite_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/invite_retrieve_response.py">InviteRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/GetOrganizationInviteSummary">client.organizations.invites.<a href="./src/gitpod/resources/organizations/invites.py">get_summary</a>(\*\*<a href="src/gitpod/types/organizations/invite_get_summary_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/invite_get_summary_response.py">InviteGetSummaryResponse</a></code>
+
+## Policies
+
+Types:
+
+```python
+from gitpod.types.organizations import OrganizationPolicies, PolicyRetrieveResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.OrganizationService/GetOrganizationPolicies">client.organizations.policies.<a href="./src/gitpod/resources/organizations/policies.py">retrieve</a>(\*\*<a href="src/gitpod/types/organizations/policy_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/policy_retrieve_response.py">PolicyRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/UpdateOrganizationPolicies">client.organizations.policies.<a href="./src/gitpod/resources/organizations/policies.py">update</a>(\*\*<a href="src/gitpod/types/organizations/policy_update_params.py">params</a>) -> object</code>
+
+## SSOConfigurations
+
+Types:
+
+```python
+from gitpod.types.organizations import (
+    ProviderType,
+    SSOConfiguration,
+    SSOConfigurationState,
+    SSOConfigurationCreateResponse,
+    SSOConfigurationRetrieveResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.OrganizationService/CreateSSOConfiguration">client.organizations.sso_configurations.<a href="./src/gitpod/resources/organizations/sso_configurations.py">create</a>(\*\*<a href="src/gitpod/types/organizations/sso_configuration_create_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/sso_configuration_create_response.py">SSOConfigurationCreateResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/GetSSOConfiguration">client.organizations.sso_configurations.<a href="./src/gitpod/resources/organizations/sso_configurations.py">retrieve</a>(\*\*<a href="src/gitpod/types/organizations/sso_configuration_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/sso_configuration_retrieve_response.py">SSOConfigurationRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.OrganizationService/UpdateSSOConfiguration">client.organizations.sso_configurations.<a href="./src/gitpod/resources/organizations/sso_configurations.py">update</a>(\*\*<a href="src/gitpod/types/organizations/sso_configuration_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.OrganizationService/ListSSOConfigurations">client.organizations.sso_configurations.<a href="./src/gitpod/resources/organizations/sso_configurations.py">list</a>(\*\*<a href="src/gitpod/types/organizations/sso_configuration_list_params.py">params</a>) -> <a href="./src/gitpod/types/organizations/sso_configuration.py">SyncSSOConfigurationsPage[SSOConfiguration]</a></code>
+- <code title="post /gitpod.v1.OrganizationService/DeleteSSOConfiguration">client.organizations.sso_configurations.<a href="./src/gitpod/resources/organizations/sso_configurations.py">delete</a>(\*\*<a href="src/gitpod/types/organizations/sso_configuration_delete_params.py">params</a>) -> object</code>
+
+# Projects
+
+Types:
+
+```python
+from gitpod.types import (
+    EnvironmentInitializer,
+    Project,
+    ProjectEnvironmentClass,
+    ProjectMetadata,
+    ProjectCreateResponse,
+    ProjectRetrieveResponse,
+    ProjectUpdateResponse,
+    ProjectCreateFromEnvironmentResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.ProjectService/CreateProject">client.projects.<a href="./src/gitpod/resources/projects/projects.py">create</a>(\*\*<a href="src/gitpod/types/project_create_params.py">params</a>) -> <a href="./src/gitpod/types/project_create_response.py">ProjectCreateResponse</a></code>
+- <code title="post /gitpod.v1.ProjectService/GetProject">client.projects.<a href="./src/gitpod/resources/projects/projects.py">retrieve</a>(\*\*<a href="src/gitpod/types/project_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/project_retrieve_response.py">ProjectRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.ProjectService/UpdateProject">client.projects.<a href="./src/gitpod/resources/projects/projects.py">update</a>(\*\*<a href="src/gitpod/types/project_update_params.py">params</a>) -> <a href="./src/gitpod/types/project_update_response.py">ProjectUpdateResponse</a></code>
+- <code title="post /gitpod.v1.ProjectService/ListProjects">client.projects.<a href="./src/gitpod/resources/projects/projects.py">list</a>(\*\*<a href="src/gitpod/types/project_list_params.py">params</a>) -> <a href="./src/gitpod/types/project.py">SyncProjectsPage[Project]</a></code>
+- <code title="post /gitpod.v1.ProjectService/DeleteProject">client.projects.<a href="./src/gitpod/resources/projects/projects.py">delete</a>(\*\*<a href="src/gitpod/types/project_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.ProjectService/CreateProjectFromEnvironment">client.projects.<a href="./src/gitpod/resources/projects/projects.py">create_from_environment</a>(\*\*<a href="src/gitpod/types/project_create_from_environment_params.py">params</a>) -> <a href="./src/gitpod/types/project_create_from_environment_response.py">ProjectCreateFromEnvironmentResponse</a></code>
+
+## Policies
+
+Types:
+
+```python
+from gitpod.types.projects import (
+    ProjectPolicy,
+    ProjectRole,
+    PolicyCreateResponse,
+    PolicyUpdateResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.ProjectService/CreateProjectPolicy">client.projects.policies.<a href="./src/gitpod/resources/projects/policies.py">create</a>(\*\*<a href="src/gitpod/types/projects/policy_create_params.py">params</a>) -> <a href="./src/gitpod/types/projects/policy_create_response.py">PolicyCreateResponse</a></code>
+- <code title="post /gitpod.v1.ProjectService/UpdateProjectPolicy">client.projects.policies.<a href="./src/gitpod/resources/projects/policies.py">update</a>(\*\*<a href="src/gitpod/types/projects/policy_update_params.py">params</a>) -> <a href="./src/gitpod/types/projects/policy_update_response.py">PolicyUpdateResponse</a></code>
+- <code title="post /gitpod.v1.ProjectService/ListProjectPolicies">client.projects.policies.<a href="./src/gitpod/resources/projects/policies.py">list</a>(\*\*<a href="src/gitpod/types/projects/policy_list_params.py">params</a>) -> <a href="./src/gitpod/types/projects/project_policy.py">SyncPoliciesPage[ProjectPolicy]</a></code>
+- <code title="post /gitpod.v1.ProjectService/DeleteProjectPolicy">client.projects.policies.<a href="./src/gitpod/resources/projects/policies.py">delete</a>(\*\*<a href="src/gitpod/types/projects/policy_delete_params.py">params</a>) -> object</code>
+
+# Runners
+
+Types:
+
+```python
+from gitpod.types import (
+    GatewayInfo,
+    LogLevel,
+    MetricsConfiguration,
+    Runner,
+    RunnerCapability,
+    RunnerConfiguration,
+    RunnerKind,
+    RunnerPhase,
+    RunnerProvider,
+    RunnerReleaseChannel,
+    RunnerSpec,
+    RunnerStatus,
+    RunnerCreateResponse,
+    RunnerRetrieveResponse,
+    RunnerCheckAuthenticationForHostResponse,
+    RunnerCreateRunnerTokenResponse,
+    RunnerParseContextURLResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.RunnerService/CreateRunner">client.runners.<a href="./src/gitpod/resources/runners/runners.py">create</a>(\*\*<a href="src/gitpod/types/runner_create_params.py">params</a>) -> <a href="./src/gitpod/types/runner_create_response.py">RunnerCreateResponse</a></code>
+- <code title="post /gitpod.v1.RunnerService/GetRunner">client.runners.<a href="./src/gitpod/resources/runners/runners.py">retrieve</a>(\*\*<a href="src/gitpod/types/runner_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/runner_retrieve_response.py">RunnerRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.RunnerService/UpdateRunner">client.runners.<a href="./src/gitpod/resources/runners/runners.py">update</a>(\*\*<a href="src/gitpod/types/runner_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.RunnerService/ListRunners">client.runners.<a href="./src/gitpod/resources/runners/runners.py">list</a>(\*\*<a href="src/gitpod/types/runner_list_params.py">params</a>) -> <a href="./src/gitpod/types/runner.py">SyncRunnersPage[Runner]</a></code>
+- <code title="post /gitpod.v1.RunnerService/DeleteRunner">client.runners.<a href="./src/gitpod/resources/runners/runners.py">delete</a>(\*\*<a href="src/gitpod/types/runner_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.RunnerService/CheckAuthenticationForHost">client.runners.<a href="./src/gitpod/resources/runners/runners.py">check_authentication_for_host</a>(\*\*<a href="src/gitpod/types/runner_check_authentication_for_host_params.py">params</a>) -> <a href="./src/gitpod/types/runner_check_authentication_for_host_response.py">RunnerCheckAuthenticationForHostResponse</a></code>
+- <code title="post /gitpod.v1.RunnerService/CreateRunnerToken">client.runners.<a href="./src/gitpod/resources/runners/runners.py">create_runner_token</a>(\*\*<a href="src/gitpod/types/runner_create_runner_token_params.py">params</a>) -> <a href="./src/gitpod/types/runner_create_runner_token_response.py">RunnerCreateRunnerTokenResponse</a></code>
+- <code title="post /gitpod.v1.RunnerService/ParseContextURL">client.runners.<a href="./src/gitpod/resources/runners/runners.py">parse_context_url</a>(\*\*<a href="src/gitpod/types/runner_parse_context_url_params.py">params</a>) -> <a href="./src/gitpod/types/runner_parse_context_url_response.py">RunnerParseContextURLResponse</a></code>
+
+## Configurations
+
+Types:
+
+```python
+from gitpod.types.runners import (
+    EnvironmentClassValidationResult,
+    FieldValidationError,
+    ScmIntegrationValidationResult,
+    ConfigurationValidateResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.RunnerConfigurationService/ValidateRunnerConfiguration">client.runners.configurations.<a href="./src/gitpod/resources/runners/configurations/configurations.py">validate</a>(\*\*<a href="src/gitpod/types/runners/configuration_validate_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configuration_validate_response.py">ConfigurationValidateResponse</a></code>
+
+### EnvironmentClasses
+
+Types:
+
+```python
+from gitpod.types.runners.configurations import (
+    EnvironmentClassCreateResponse,
+    EnvironmentClassRetrieveResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.RunnerConfigurationService/CreateEnvironmentClass">client.runners.configurations.environment_classes.<a href="./src/gitpod/resources/runners/configurations/environment_classes.py">create</a>(\*\*<a href="src/gitpod/types/runners/configurations/environment_class_create_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/environment_class_create_response.py">EnvironmentClassCreateResponse</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/GetEnvironmentClass">client.runners.configurations.environment_classes.<a href="./src/gitpod/resources/runners/configurations/environment_classes.py">retrieve</a>(\*\*<a href="src/gitpod/types/runners/configurations/environment_class_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/environment_class_retrieve_response.py">EnvironmentClassRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/UpdateEnvironmentClass">client.runners.configurations.environment_classes.<a href="./src/gitpod/resources/runners/configurations/environment_classes.py">update</a>(\*\*<a href="src/gitpod/types/runners/configurations/environment_class_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/ListEnvironmentClasses">client.runners.configurations.environment_classes.<a href="./src/gitpod/resources/runners/configurations/environment_classes.py">list</a>(\*\*<a href="src/gitpod/types/runners/configurations/environment_class_list_params.py">params</a>) -> <a href="./src/gitpod/types/shared/environment_class.py">SyncEnvironmentClassesPage[EnvironmentClass]</a></code>
+
+### HostAuthenticationTokens
+
+Types:
+
+```python
+from gitpod.types.runners.configurations import (
+    HostAuthenticationToken,
+    HostAuthenticationTokenSource,
+    HostAuthenticationTokenCreateResponse,
+    HostAuthenticationTokenRetrieveResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.RunnerConfigurationService/CreateHostAuthenticationToken">client.runners.configurations.host_authentication_tokens.<a href="./src/gitpod/resources/runners/configurations/host_authentication_tokens.py">create</a>(\*\*<a href="src/gitpod/types/runners/configurations/host_authentication_token_create_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/host_authentication_token_create_response.py">HostAuthenticationTokenCreateResponse</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/GetHostAuthenticationToken">client.runners.configurations.host_authentication_tokens.<a href="./src/gitpod/resources/runners/configurations/host_authentication_tokens.py">retrieve</a>(\*\*<a href="src/gitpod/types/runners/configurations/host_authentication_token_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/host_authentication_token_retrieve_response.py">HostAuthenticationTokenRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/UpdateHostAuthenticationToken">client.runners.configurations.host_authentication_tokens.<a href="./src/gitpod/resources/runners/configurations/host_authentication_tokens.py">update</a>(\*\*<a href="src/gitpod/types/runners/configurations/host_authentication_token_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/ListHostAuthenticationTokens">client.runners.configurations.host_authentication_tokens.<a href="./src/gitpod/resources/runners/configurations/host_authentication_tokens.py">list</a>(\*\*<a href="src/gitpod/types/runners/configurations/host_authentication_token_list_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/host_authentication_token.py">SyncTokensPage[HostAuthenticationToken]</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/DeleteHostAuthenticationToken">client.runners.configurations.host_authentication_tokens.<a href="./src/gitpod/resources/runners/configurations/host_authentication_tokens.py">delete</a>(\*\*<a href="src/gitpod/types/runners/configurations/host_authentication_token_delete_params.py">params</a>) -> object</code>
+
+### Schema
+
+Types:
+
+```python
+from gitpod.types.runners.configurations import RunnerConfigurationSchema, SchemaRetrieveResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.RunnerConfigurationService/GetRunnerConfigurationSchema">client.runners.configurations.schema.<a href="./src/gitpod/resources/runners/configurations/schema.py">retrieve</a>(\*\*<a href="src/gitpod/types/runners/configurations/schema_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/schema_retrieve_response.py">SchemaRetrieveResponse</a></code>
+
+### ScmIntegrations
+
+Types:
+
+```python
+from gitpod.types.runners.configurations import (
+    ScmIntegration,
+    ScmIntegrationOAuthConfig,
+    ScmIntegrationCreateResponse,
+    ScmIntegrationRetrieveResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.RunnerConfigurationService/CreateSCMIntegration">client.runners.configurations.scm_integrations.<a href="./src/gitpod/resources/runners/configurations/scm_integrations.py">create</a>(\*\*<a href="src/gitpod/types/runners/configurations/scm_integration_create_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/scm_integration_create_response.py">ScmIntegrationCreateResponse</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/GetSCMIntegration">client.runners.configurations.scm_integrations.<a href="./src/gitpod/resources/runners/configurations/scm_integrations.py">retrieve</a>(\*\*<a href="src/gitpod/types/runners/configurations/scm_integration_retrieve_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/scm_integration_retrieve_response.py">ScmIntegrationRetrieveResponse</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/UpdateSCMIntegration">client.runners.configurations.scm_integrations.<a href="./src/gitpod/resources/runners/configurations/scm_integrations.py">update</a>(\*\*<a href="src/gitpod/types/runners/configurations/scm_integration_update_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/ListSCMIntegrations">client.runners.configurations.scm_integrations.<a href="./src/gitpod/resources/runners/configurations/scm_integrations.py">list</a>(\*\*<a href="src/gitpod/types/runners/configurations/scm_integration_list_params.py">params</a>) -> <a href="./src/gitpod/types/runners/configurations/scm_integration.py">SyncIntegrationsPage[ScmIntegration]</a></code>
+- <code title="post /gitpod.v1.RunnerConfigurationService/DeleteSCMIntegration">client.runners.configurations.scm_integrations.<a href="./src/gitpod/resources/runners/configurations/scm_integrations.py">delete</a>(\*\*<a href="src/gitpod/types/runners/configurations/scm_integration_delete_params.py">params</a>) -> object</code>
+
+## Policies
+
+Types:
+
+```python
+from gitpod.types.runners import (
+    RunnerPolicy,
+    RunnerRole,
+    PolicyCreateResponse,
+    PolicyUpdateResponse,
+)
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.RunnerService/CreateRunnerPolicy">client.runners.policies.<a href="./src/gitpod/resources/runners/policies.py">create</a>(\*\*<a href="src/gitpod/types/runners/policy_create_params.py">params</a>) -> <a href="./src/gitpod/types/runners/policy_create_response.py">PolicyCreateResponse</a></code>
+- <code title="post /gitpod.v1.RunnerService/UpdateRunnerPolicy">client.runners.policies.<a href="./src/gitpod/resources/runners/policies.py">update</a>(\*\*<a href="src/gitpod/types/runners/policy_update_params.py">params</a>) -> <a href="./src/gitpod/types/runners/policy_update_response.py">PolicyUpdateResponse</a></code>
+- <code title="post /gitpod.v1.RunnerService/ListRunnerPolicies">client.runners.policies.<a href="./src/gitpod/resources/runners/policies.py">list</a>(\*\*<a href="src/gitpod/types/runners/policy_list_params.py">params</a>) -> <a href="./src/gitpod/types/runners/runner_policy.py">SyncPoliciesPage[RunnerPolicy]</a></code>
+- <code title="post /gitpod.v1.RunnerService/DeleteRunnerPolicy">client.runners.policies.<a href="./src/gitpod/resources/runners/policies.py">delete</a>(\*\*<a href="src/gitpod/types/runners/policy_delete_params.py">params</a>) -> object</code>
+
+# Secrets
+
+Types:
+
+```python
+from gitpod.types import Secret, SecretScope, SecretCreateResponse, SecretGetValueResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.SecretService/CreateSecret">client.secrets.<a href="./src/gitpod/resources/secrets.py">create</a>(\*\*<a href="src/gitpod/types/secret_create_params.py">params</a>) -> <a href="./src/gitpod/types/secret_create_response.py">SecretCreateResponse</a></code>
+- <code title="post /gitpod.v1.SecretService/ListSecrets">client.secrets.<a href="./src/gitpod/resources/secrets.py">list</a>(\*\*<a href="src/gitpod/types/secret_list_params.py">params</a>) -> <a href="./src/gitpod/types/secret.py">SyncSecretsPage[Secret]</a></code>
+- <code title="post /gitpod.v1.SecretService/DeleteSecret">client.secrets.<a href="./src/gitpod/resources/secrets.py">delete</a>(\*\*<a href="src/gitpod/types/secret_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.SecretService/GetSecretValue">client.secrets.<a href="./src/gitpod/resources/secrets.py">get_value</a>(\*\*<a href="src/gitpod/types/secret_get_value_params.py">params</a>) -> <a href="./src/gitpod/types/secret_get_value_response.py">SecretGetValueResponse</a></code>
+- <code title="post /gitpod.v1.SecretService/UpdateSecretValue">client.secrets.<a href="./src/gitpod/resources/secrets.py">update_value</a>(\*\*<a href="src/gitpod/types/secret_update_value_params.py">params</a>) -> object</code>
+
+# Usage
+
+Types:
+
+```python
+from gitpod.types import EnvironmentUsageRecord
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.UsageService/ListEnvironmentUsageRecords">client.usage.<a href="./src/gitpod/resources/usage.py">list_environment_runtime_records</a>(\*\*<a href="src/gitpod/types/usage_list_environment_runtime_records_params.py">params</a>) -> <a href="./src/gitpod/types/environment_usage_record.py">SyncRecordsPage[EnvironmentUsageRecord]</a></code>
+
+# Users
+
+Types:
+
+```python
+from gitpod.types import User, UserGetAuthenticatedUserResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.UserService/GetAuthenticatedUser">client.users.<a href="./src/gitpod/resources/users/users.py">get_authenticated_user</a>(\*\*<a href="src/gitpod/types/user_get_authenticated_user_params.py">params</a>) -> <a href="./src/gitpod/types/user_get_authenticated_user_response.py">UserGetAuthenticatedUserResponse</a></code>
+- <code title="post /gitpod.v1.UserService/SetSuspended">client.users.<a href="./src/gitpod/resources/users/users.py">set_suspended</a>(\*\*<a href="src/gitpod/types/user_set_suspended_params.py">params</a>) -> object</code>
+
+## Dotfiles
+
+Types:
+
+```python
+from gitpod.types.users import DotfilesConfiguration, DotfileGetResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.UserService/GetDotfilesConfiguration">client.users.dotfiles.<a href="./src/gitpod/resources/users/dotfiles.py">get</a>(\*\*<a href="src/gitpod/types/users/dotfile_get_params.py">params</a>) -> <a href="./src/gitpod/types/users/dotfile_get_response.py">DotfileGetResponse</a></code>
+- <code title="post /gitpod.v1.UserService/SetDotfilesConfiguration">client.users.dotfiles.<a href="./src/gitpod/resources/users/dotfiles.py">set</a>(\*\*<a href="src/gitpod/types/users/dotfile_set_params.py">params</a>) -> object</code>
+
+## Pats
+
+Types:
+
+```python
+from gitpod.types.users import PersonalAccessToken, PatGetResponse
+```
+
+Methods:
+
+- <code title="post /gitpod.v1.UserService/ListPersonalAccessTokens">client.users.pats.<a href="./src/gitpod/resources/users/pats.py">list</a>(\*\*<a href="src/gitpod/types/users/pat_list_params.py">params</a>) -> <a href="./src/gitpod/types/users/personal_access_token.py">SyncPersonalAccessTokensPage[PersonalAccessToken]</a></code>
+- <code title="post /gitpod.v1.UserService/DeletePersonalAccessToken">client.users.pats.<a href="./src/gitpod/resources/users/pats.py">delete</a>(\*\*<a href="src/gitpod/types/users/pat_delete_params.py">params</a>) -> object</code>
+- <code title="post /gitpod.v1.UserService/GetPersonalAccessToken">client.users.pats.<a href="./src/gitpod/resources/users/pats.py">get</a>(\*\*<a href="src/gitpod/types/users/pat_get_params.py">params</a>) -> <a href="./src/gitpod/types/users/pat_get_response.py">PatGetResponse</a></code>
diff --git a/bin/check-release-environment b/bin/check-release-environment
new file mode 100644
index 0000000..b845b0f
--- /dev/null
+++ b/bin/check-release-environment
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+errors=()
+
+if [ -z "${PYPI_TOKEN}" ]; then
+  errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
+fi
+
+lenErrors=${#errors[@]}
+
+if [[ lenErrors -gt 0 ]]; then
+  echo -e "Found the following errors in the release environment:\n"
+
+  for error in "${errors[@]}"; do
+    echo -e "- $error\n"
+  done
+
+  exit 1
+fi
+
+echo "The environment is ready to push releases!"
diff --git a/bin/publish-pypi b/bin/publish-pypi
new file mode 100644
index 0000000..826054e
--- /dev/null
+++ b/bin/publish-pypi
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -eux
+mkdir -p dist
+rye build --clean
+rye publish --yes --token=$PYPI_TOKEN
diff --git a/examples/.keep b/examples/.keep
new file mode 100644
index 0000000..d8c73e9
--- /dev/null
+++ b/examples/.keep
@@ -0,0 +1,4 @@
+File generated from our OpenAPI spec by Stainless.
+
+This directory can be used to store example files demonstrating usage of this SDK.
+It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
\ No newline at end of file
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..e2d1320
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,50 @@
+[mypy]
+pretty = True
+show_error_codes = True
+
+# Exclude _files.py because mypy isn't smart enough to apply
+# the correct type narrowing and as this is an internal module
+# it's fine to just use Pyright.
+#
+# We also exclude our `tests` as mypy doesn't always infer
+# types correctly and Pyright will still catch any type errors.
+exclude = ^(src/gitpod/_files\.py|_dev/.*\.py|tests/.*)$
+
+strict_equality = True
+implicit_reexport = True
+check_untyped_defs = True
+no_implicit_optional = True
+
+warn_return_any = True
+warn_unreachable = True
+warn_unused_configs = True
+
+# Turn these options off as it could cause conflicts
+# with the Pyright options.
+warn_unused_ignores = False
+warn_redundant_casts = False
+
+disallow_any_generics = True
+disallow_untyped_defs = True
+disallow_untyped_calls = True
+disallow_subclassing_any = True
+disallow_incomplete_defs = True
+disallow_untyped_decorators = True
+cache_fine_grained = True
+
+# By default, mypy reports an error if you assign a value to the result
+# of a function call that doesn't return anything. We do this in our test
+# cases:
+# ```
+# result = ...
+# assert result is None
+# ```
+# Changing this codegen to make mypy happy would increase complexity
+# and would not be worth it.
+disable_error_code = func-returns-value,overload-cannot-match
+
+# https://github.com/python/mypy/issues/12162
+[mypy.overrides]
+module = "black.files.*"
+ignore_errors = true
+ignore_missing_imports = true
diff --git a/noxfile.py b/noxfile.py
new file mode 100644
index 0000000..53bca7f
--- /dev/null
+++ b/noxfile.py
@@ -0,0 +1,9 @@
+import nox
+
+
+@nox.session(reuse_venv=True, name="test-pydantic-v1")
+def test_pydantic_v1(session: nox.Session) -> None:
+    session.install("-r", "requirements-dev.lock")
+    session.install("pydantic<2")
+
+    session.run("pytest", "--showlocals", "--ignore=tests/functional", *session.posargs)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..5f4250e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,211 @@
+[project]
+name = "gitpod-sdk"
+version = "0.3.0"
+description = "The official Python library for the gitpod API"
+dynamic = ["readme"]
+license = "Apache-2.0"
+authors = [
+{ name = "Gitpod", email = "dev-feedback@gitpod.com" },
+]
+dependencies = [
+    "httpx>=0.23.0, <1",
+    "pydantic>=1.9.0, <3",
+    "typing-extensions>=4.10, <5",
+    "anyio>=3.5.0, <5",
+    "distro>=1.7.0, <2",
+    "sniffio",
+]
+requires-python = ">= 3.8"
+classifiers = [
+  "Typing :: Typed",
+  "Intended Audience :: Developers",
+  "Programming Language :: Python :: 3.8",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: 3.12",
+  "Programming Language :: Python :: 3.13",
+  "Operating System :: OS Independent",
+  "Operating System :: POSIX",
+  "Operating System :: MacOS",
+  "Operating System :: POSIX :: Linux",
+  "Operating System :: Microsoft :: Windows",
+  "Topic :: Software Development :: Libraries :: Python Modules",
+  "License :: OSI Approved :: Apache Software License"
+]
+
+[project.urls]
+Homepage = "https://github.com/gitpod-io/gitpod-sdk-python"
+Repository = "https://github.com/gitpod-io/gitpod-sdk-python"
+
+[project.optional-dependencies]
+aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
+
+[tool.rye]
+managed = true
+# version pins are in requirements-dev.lock
+dev-dependencies = [
+    "pyright==1.1.399",
+    "mypy",
+    "respx",
+    "pytest",
+    "pytest-asyncio",
+    "ruff",
+    "time-machine",
+    "nox",
+    "dirty-equals>=0.6.0",
+    "importlib-metadata>=6.7.0",
+    "rich>=13.7.1",
+    "nest_asyncio==1.6.0",
+    "pytest-xdist>=3.6.1",
+]
+
+[tool.rye.scripts]
+format = { chain = [
+  "format:ruff",
+  "format:docs",
+  "fix:ruff",
+  # run formatting again to fix any inconsistencies when imports are stripped
+  "format:ruff",
+]}
+"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md"
+"format:ruff" = "ruff format"
+
+"lint" = { chain = [
+  "check:ruff",
+  "typecheck",
+  "check:importable",
+]}
+"check:ruff" = "ruff check ."
+"fix:ruff" = "ruff check --fix ."
+
+"check:importable" = "python -c 'import gitpod'"
+
+typecheck = { chain = [
+  "typecheck:pyright",
+  "typecheck:mypy"
+]}
+"typecheck:pyright" = "pyright"
+"typecheck:verify-types" = "pyright --verifytypes gitpod --ignoreexternal"
+"typecheck:mypy" = "mypy ."
+
+[build-system]
+requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build]
+include = [
+  "src/*"
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/gitpod"]
+
+[tool.hatch.build.targets.sdist]
+# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc)
+include = [
+  "/*.toml",
+  "/*.json",
+  "/*.lock",
+  "/*.md",
+  "/mypy.ini",
+  "/noxfile.py",
+  "bin/*",
+  "examples/*",
+  "src/*",
+  "tests/*",
+]
+
+[tool.hatch.metadata.hooks.fancy-pypi-readme]
+content-type = "text/markdown"
+
+[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
+path = "README.md"
+
+[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
+# replace relative links with absolute links
+pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
+replacement = '[\1](https://github.com/gitpod-io/gitpod-sdk-python/tree/main/\g<2>)'
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+addopts = "--tb=short -n auto"
+xfail_strict = true
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "session"
+filterwarnings = [
+  "error"
+]
+
+[tool.pyright]
+# this enables practically every flag given by pyright.
+# there are a couple of flags that are still disabled by
+# default in strict mode as they are experimental and niche.
+typeCheckingMode = "strict"
+pythonVersion = "3.8"
+
+exclude = [
+    "_dev",
+    ".venv",
+    ".nox",
+]
+
+reportImplicitOverride = true
+reportOverlappingOverload = false
+
+reportImportCycles = false
+reportPrivateUsage = false
+
+[tool.ruff]
+line-length = 120
+output-format = "grouped"
+target-version = "py37"
+
+[tool.ruff.format]
+docstring-code-format = true
+
+[tool.ruff.lint]
+select = [
+  # isort
+  "I",
+  # bugbear rules
+  "B",
+  # remove unused imports
+  "F401",
+  # bare except statements
+  "E722",
+  # unused arguments
+  "ARG",
+  # print statements
+  "T201",
+  "T203",
+  # misuse of typing.TYPE_CHECKING
+  "TC004",
+  # import rules
+  "TID251",
+]
+ignore = [
+  # mutable defaults
+  "B006",
+]
+unfixable = [
+  # disable auto fix for print statements
+  "T201",
+  "T203",
+]
+
+[tool.ruff.lint.flake8-tidy-imports.banned-api]
+"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead"
+
+[tool.ruff.lint.isort]
+length-sort = true
+length-sort-straight = true
+combine-as-imports = true
+extra-standard-library = ["typing_extensions"]
+known-first-party = ["gitpod", "tests"]
+
+[tool.ruff.lint.per-file-ignores]
+"bin/**.py" = ["T201", "T203"]
+"scripts/**.py" = ["T201", "T203"]
+"tests/**.py" = ["T201", "T203"]
+"examples/**.py" = ["T201", "T203"]
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000..024bdf6
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,66 @@
+{
+  "packages": {
+    ".": {}
+  },
+  "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json",
+  "include-v-in-tag": true,
+  "include-component-in-tag": false,
+  "versioning": "prerelease",
+  "prerelease": true,
+  "bump-minor-pre-major": true,
+  "bump-patch-for-minor-pre-major": false,
+  "pull-request-header": "Automated Release PR",
+  "pull-request-title-pattern": "release: ${version}",
+  "changelog-sections": [
+    {
+      "type": "feat",
+      "section": "Features"
+    },
+    {
+      "type": "fix",
+      "section": "Bug Fixes"
+    },
+    {
+      "type": "perf",
+      "section": "Performance Improvements"
+    },
+    {
+      "type": "revert",
+      "section": "Reverts"
+    },
+    {
+      "type": "chore",
+      "section": "Chores"
+    },
+    {
+      "type": "docs",
+      "section": "Documentation"
+    },
+    {
+      "type": "style",
+      "section": "Styles"
+    },
+    {
+      "type": "refactor",
+      "section": "Refactors"
+    },
+    {
+      "type": "test",
+      "section": "Tests",
+      "hidden": true
+    },
+    {
+      "type": "build",
+      "section": "Build System"
+    },
+    {
+      "type": "ci",
+      "section": "Continuous Integration",
+      "hidden": true
+    }
+  ],
+  "release-type": "python",
+  "extra-files": [
+    "src/gitpod/_version.py"
+  ]
+}
\ No newline at end of file
diff --git a/requirements-dev.lock b/requirements-dev.lock
new file mode 100644
index 0000000..d496425
--- /dev/null
+++ b/requirements-dev.lock
@@ -0,0 +1,135 @@
+# generated by rye
+# use `rye lock` or `rye sync` to update this lockfile
+#
+# last locked with the following flags:
+#   pre: false
+#   features: []
+#   all-features: true
+#   with-sources: false
+#   generate-hashes: false
+#   universal: false
+
+-e file:.
+aiohappyeyeballs==2.6.1
+    # via aiohttp
+aiohttp==3.12.8
+    # via gitpod-sdk
+    # via httpx-aiohttp
+aiosignal==1.3.2
+    # via aiohttp
+annotated-types==0.6.0
+    # via pydantic
+anyio==4.4.0
+    # via gitpod-sdk
+    # via httpx
+argcomplete==3.1.2
+    # via nox
+async-timeout==5.0.1
+    # via aiohttp
+attrs==25.3.0
+    # via aiohttp
+certifi==2023.7.22
+    # via httpcore
+    # via httpx
+colorlog==6.7.0
+    # via nox
+dirty-equals==0.6.0
+distlib==0.3.7
+    # via virtualenv
+distro==1.8.0
+    # via gitpod-sdk
+exceptiongroup==1.2.2
+    # via anyio
+    # via pytest
+execnet==2.1.1
+    # via pytest-xdist
+filelock==3.12.4
+    # via virtualenv
+frozenlist==1.6.2
+    # via aiohttp
+    # via aiosignal
+h11==0.16.0
+    # via httpcore
+httpcore==1.0.9
+    # via httpx
+httpx==0.28.1
+    # via gitpod-sdk
+    # via httpx-aiohttp
+    # via respx
+httpx-aiohttp==0.1.8
+    # via gitpod-sdk
+idna==3.4
+    # via anyio
+    # via httpx
+    # via yarl
+importlib-metadata==7.0.0
+iniconfig==2.0.0
+    # via pytest
+markdown-it-py==3.0.0
+    # via rich
+mdurl==0.1.2
+    # via markdown-it-py
+multidict==6.4.4
+    # via aiohttp
+    # via yarl
+mypy==1.14.1
+mypy-extensions==1.0.0
+    # via mypy
+nest-asyncio==1.6.0
+nodeenv==1.8.0
+    # via pyright
+nox==2023.4.22
+packaging==23.2
+    # via nox
+    # via pytest
+platformdirs==3.11.0
+    # via virtualenv
+pluggy==1.5.0
+    # via pytest
+propcache==0.3.1
+    # via aiohttp
+    # via yarl
+pydantic==2.10.3
+    # via gitpod-sdk
+pydantic-core==2.27.1
+    # via pydantic
+pygments==2.18.0
+    # via rich
+pyright==1.1.399
+pytest==8.3.3
+    # via pytest-asyncio
+    # via pytest-xdist
+pytest-asyncio==0.24.0
+pytest-xdist==3.7.0
+python-dateutil==2.8.2
+    # via time-machine
+pytz==2023.3.post1
+    # via dirty-equals
+respx==0.22.0
+rich==13.7.1
+ruff==0.9.4
+setuptools==68.2.2
+    # via nodeenv
+six==1.16.0
+    # via python-dateutil
+sniffio==1.3.0
+    # via anyio
+    # via gitpod-sdk
+time-machine==2.9.0
+tomli==2.0.2
+    # via mypy
+    # via pytest
+typing-extensions==4.12.2
+    # via anyio
+    # via gitpod-sdk
+    # via multidict
+    # via mypy
+    # via pydantic
+    # via pydantic-core
+    # via pyright
+virtualenv==20.24.5
+    # via nox
+yarl==1.20.0
+    # via aiohttp
+zipp==3.17.0
+    # via importlib-metadata
diff --git a/requirements.lock b/requirements.lock
new file mode 100644
index 0000000..c0455c9
--- /dev/null
+++ b/requirements.lock
@@ -0,0 +1,72 @@
+# generated by rye
+# use `rye lock` or `rye sync` to update this lockfile
+#
+# last locked with the following flags:
+#   pre: false
+#   features: []
+#   all-features: true
+#   with-sources: false
+#   generate-hashes: false
+#   universal: false
+
+-e file:.
+aiohappyeyeballs==2.6.1
+    # via aiohttp
+aiohttp==3.12.8
+    # via gitpod-sdk
+    # via httpx-aiohttp
+aiosignal==1.3.2
+    # via aiohttp
+annotated-types==0.6.0
+    # via pydantic
+anyio==4.4.0
+    # via gitpod-sdk
+    # via httpx
+async-timeout==5.0.1
+    # via aiohttp
+attrs==25.3.0
+    # via aiohttp
+certifi==2023.7.22
+    # via httpcore
+    # via httpx
+distro==1.8.0
+    # via gitpod-sdk
+exceptiongroup==1.2.2
+    # via anyio
+frozenlist==1.6.2
+    # via aiohttp
+    # via aiosignal
+h11==0.16.0
+    # via httpcore
+httpcore==1.0.9
+    # via httpx
+httpx==0.28.1
+    # via gitpod-sdk
+    # via httpx-aiohttp
+httpx-aiohttp==0.1.8
+    # via gitpod-sdk
+idna==3.4
+    # via anyio
+    # via httpx
+    # via yarl
+multidict==6.4.4
+    # via aiohttp
+    # via yarl
+propcache==0.3.1
+    # via aiohttp
+    # via yarl
+pydantic==2.10.3
+    # via gitpod-sdk
+pydantic-core==2.27.1
+    # via pydantic
+sniffio==1.3.0
+    # via anyio
+    # via gitpod-sdk
+typing-extensions==4.12.2
+    # via anyio
+    # via gitpod-sdk
+    # via multidict
+    # via pydantic
+    # via pydantic-core
+yarl==1.20.0
+    # via aiohttp
diff --git a/scripts/bootstrap b/scripts/bootstrap
new file mode 100755
index 0000000..e84fe62
--- /dev/null
+++ b/scripts/bootstrap
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
+  brew bundle check >/dev/null 2>&1 || {
+    echo "==> Installing Homebrew dependencies…"
+    brew bundle
+  }
+fi
+
+echo "==> Installing Python dependencies…"
+
+# experimental uv support makes installations significantly faster
+rye config --set-bool behavior.use-uv=true
+
+rye sync --all-features
diff --git a/scripts/format b/scripts/format
new file mode 100755
index 0000000..667ec2d
--- /dev/null
+++ b/scripts/format
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+echo "==> Running formatters"
+rye run format
diff --git a/scripts/lint b/scripts/lint
new file mode 100755
index 0000000..6a98ee4
--- /dev/null
+++ b/scripts/lint
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+echo "==> Running lints"
+rye run lint
+
+echo "==> Making sure it imports"
+rye run python -c 'import gitpod'
diff --git a/scripts/mock b/scripts/mock
new file mode 100755
index 0000000..d2814ae
--- /dev/null
+++ b/scripts/mock
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+if [[ -n "$1" && "$1" != '--'* ]]; then
+  URL="$1"
+  shift
+else
+  URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)"
+fi
+
+# Check if the URL is empty
+if [ -z "$URL" ]; then
+  echo "Error: No OpenAPI spec path/url provided or found in .stats.yml"
+  exit 1
+fi
+
+echo "==> Starting mock server with URL ${URL}"
+
+# Run prism mock on the given spec
+if [ "$1" == "--daemon" ]; then
+  npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log &
+
+  # Wait for server to come online
+  echo -n "Waiting for server"
+  while ! grep -q "✖  fatal\|Prism is listening" ".prism.log" ; do
+    echo -n "."
+    sleep 0.1
+  done
+
+  if grep -q "✖  fatal" ".prism.log"; then
+    cat .prism.log
+    exit 1
+  fi
+
+  echo
+else
+  npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL"
+fi
diff --git a/scripts/test b/scripts/test
new file mode 100755
index 0000000..2b87845
--- /dev/null
+++ b/scripts/test
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+NC='\033[0m' # No Color
+
+function prism_is_running() {
+  curl --silent "http://localhost:4010" >/dev/null 2>&1
+}
+
+kill_server_on_port() {
+  pids=$(lsof -t -i tcp:"$1" || echo "")
+  if [ "$pids" != "" ]; then
+    kill "$pids"
+    echo "Stopped $pids."
+  fi
+}
+
+function is_overriding_api_base_url() {
+  [ -n "$TEST_API_BASE_URL" ]
+}
+
+if ! is_overriding_api_base_url && ! prism_is_running ; then
+  # When we exit this script, make sure to kill the background mock server process
+  trap 'kill_server_on_port 4010' EXIT
+
+  # Start the dev server
+  ./scripts/mock --daemon
+fi
+
+if is_overriding_api_base_url ; then
+  echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}"
+  echo
+elif ! prism_is_running ; then
+  echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server"
+  echo -e "running against your OpenAPI spec."
+  echo
+  echo -e "To run the server, pass in the path or url of your OpenAPI"
+  echo -e "spec to the prism command:"
+  echo
+  echo -e "  \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}"
+  echo
+
+  exit 1
+else
+  echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}"
+  echo
+fi
+
+export DEFER_PYDANTIC_BUILD=false
+
+echo "==> Running tests"
+rye run pytest "$@"
+
+echo "==> Running Pydantic v1 tests"
+rye run nox -s test-pydantic-v1 -- "$@"
diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py
new file mode 100644
index 0000000..0cf2bd2
--- /dev/null
+++ b/scripts/utils/ruffen-docs.py
@@ -0,0 +1,167 @@
+# fork of https://github.com/asottile/blacken-docs adapted for ruff
+from __future__ import annotations
+
+import re
+import sys
+import argparse
+import textwrap
+import contextlib
+import subprocess
+from typing import Match, Optional, Sequence, Generator, NamedTuple, cast
+
+MD_RE = re.compile(
+    r"(?P<before>^(?P<indent> *)```\s*python\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```\s*$)",
+    re.DOTALL | re.MULTILINE,
+)
+MD_PYCON_RE = re.compile(
+    r"(?P<before>^(?P<indent> *)```\s*pycon\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```.*$)",
+    re.DOTALL | re.MULTILINE,
+)
+PYCON_PREFIX = ">>> "
+PYCON_CONTINUATION_PREFIX = "..."
+PYCON_CONTINUATION_RE = re.compile(
+    rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)",
+)
+DEFAULT_LINE_LENGTH = 100
+
+
+class CodeBlockError(NamedTuple):
+    offset: int
+    exc: Exception
+
+
+def format_str(
+    src: str,
+) -> tuple[str, Sequence[CodeBlockError]]:
+    errors: list[CodeBlockError] = []
+
+    @contextlib.contextmanager
+    def _collect_error(match: Match[str]) -> Generator[None, None, None]:
+        try:
+            yield
+        except Exception as e:
+            errors.append(CodeBlockError(match.start(), e))
+
+    def _md_match(match: Match[str]) -> str:
+        code = textwrap.dedent(match["code"])
+        with _collect_error(match):
+            code = format_code_block(code)
+        code = textwrap.indent(code, match["indent"])
+        return f"{match['before']}{code}{match['after']}"
+
+    def _pycon_match(match: Match[str]) -> str:
+        code = ""
+        fragment = cast(Optional[str], None)
+
+        def finish_fragment() -> None:
+            nonlocal code
+            nonlocal fragment
+
+            if fragment is not None:
+                with _collect_error(match):
+                    fragment = format_code_block(fragment)
+                fragment_lines = fragment.splitlines()
+                code += f"{PYCON_PREFIX}{fragment_lines[0]}\n"
+                for line in fragment_lines[1:]:
+                    # Skip blank lines to handle Black adding a blank above
+                    # functions within blocks. A blank line would end the REPL
+                    # continuation prompt.
+                    #
+                    # >>> if True:
+                    # ...     def f():
+                    # ...         pass
+                    # ...
+                    if line:
+                        code += f"{PYCON_CONTINUATION_PREFIX} {line}\n"
+                if fragment_lines[-1].startswith(" "):
+                    code += f"{PYCON_CONTINUATION_PREFIX}\n"
+                fragment = None
+
+        indentation = None
+        for line in match["code"].splitlines():
+            orig_line, line = line, line.lstrip()
+            if indentation is None and line:
+                indentation = len(orig_line) - len(line)
+            continuation_match = PYCON_CONTINUATION_RE.match(line)
+            if continuation_match and fragment is not None:
+                fragment += line[continuation_match.end() :] + "\n"
+            else:
+                finish_fragment()
+                if line.startswith(PYCON_PREFIX):
+                    fragment = line[len(PYCON_PREFIX) :] + "\n"
+                else:
+                    code += orig_line[indentation:] + "\n"
+        finish_fragment()
+        return code
+
+    def _md_pycon_match(match: Match[str]) -> str:
+        code = _pycon_match(match)
+        code = textwrap.indent(code, match["indent"])
+        return f"{match['before']}{code}{match['after']}"
+
+    src = MD_RE.sub(_md_match, src)
+    src = MD_PYCON_RE.sub(_md_pycon_match, src)
+    return src, errors
+
+
+def format_code_block(code: str) -> str:
+    return subprocess.check_output(
+        [
+            sys.executable,
+            "-m",
+            "ruff",
+            "format",
+            "--stdin-filename=script.py",
+            f"--line-length={DEFAULT_LINE_LENGTH}",
+        ],
+        encoding="utf-8",
+        input=code,
+    )
+
+
+def format_file(
+    filename: str,
+    skip_errors: bool,
+) -> int:
+    with open(filename, encoding="UTF-8") as f:
+        contents = f.read()
+    new_contents, errors = format_str(contents)
+    for error in errors:
+        lineno = contents[: error.offset].count("\n") + 1
+        print(f"{filename}:{lineno}: code block parse error {error.exc}")
+    if errors and not skip_errors:
+        return 1
+    if contents != new_contents:
+        print(f"{filename}: Rewriting...")
+        with open(filename, "w", encoding="UTF-8") as f:
+            f.write(new_contents)
+        return 0
+    else:
+        return 0
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "-l",
+        "--line-length",
+        type=int,
+        default=DEFAULT_LINE_LENGTH,
+    )
+    parser.add_argument(
+        "-S",
+        "--skip-string-normalization",
+        action="store_true",
+    )
+    parser.add_argument("-E", "--skip-errors", action="store_true")
+    parser.add_argument("filenames", nargs="*")
+    args = parser.parse_args(argv)
+
+    retv = 0
+    for filename in args.filenames:
+        retv |= format_file(filename, skip_errors=args.skip_errors)
+    return retv
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
new file mode 100755
index 0000000..ea68293
--- /dev/null
+++ b/scripts/utils/upload-artifact.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+set -exuo pipefail
+
+FILENAME=$(basename dist/*.whl)
+
+RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \
+  -H "Authorization: Bearer $AUTH" \
+  -H "Content-Type: application/json")
+
+SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url')
+
+if [[ "$SIGNED_URL" == "null" ]]; then
+  echo -e "\033[31mFailed to get signed URL.\033[0m"
+  exit 1
+fi
+
+UPLOAD_RESPONSE=$(curl -v -X PUT \
+  -H "Content-Type: binary/octet-stream" \
+  --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1)
+
+if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
+  echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
+  echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/gitpod-python/$SHA/$FILENAME'\033[0m"
+else
+  echo -e "\033[31mFailed to upload artifact.\033[0m"
+  exit 1
+fi
diff --git a/src/gitpod/__init__.py b/src/gitpod/__init__.py
new file mode 100644
index 0000000..f2a5eb2
--- /dev/null
+++ b/src/gitpod/__init__.py
@@ -0,0 +1,90 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import typing as _t
+
+from . import types
+from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes
+from ._utils import file_from_path
+from ._client import Client, Gitpod, Stream, Timeout, Transport, AsyncClient, AsyncGitpod, AsyncStream, RequestOptions
+from ._models import BaseModel
+from ._version import __title__, __version__
+from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
+from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
+from ._exceptions import (
+    APIError,
+    GitpodError,
+    ConflictError,
+    NotFoundError,
+    APIStatusError,
+    RateLimitError,
+    APITimeoutError,
+    BadRequestError,
+    APIConnectionError,
+    AuthenticationError,
+    InternalServerError,
+    PermissionDeniedError,
+    UnprocessableEntityError,
+    APIResponseValidationError,
+)
+from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
+from ._utils._logs import setup_logging as _setup_logging
+
+__all__ = [
+    "types",
+    "__version__",
+    "__title__",
+    "NoneType",
+    "Transport",
+    "ProxiesTypes",
+    "NotGiven",
+    "NOT_GIVEN",
+    "Omit",
+    "GitpodError",
+    "APIError",
+    "APIStatusError",
+    "APITimeoutError",
+    "APIConnectionError",
+    "APIResponseValidationError",
+    "BadRequestError",
+    "AuthenticationError",
+    "PermissionDeniedError",
+    "NotFoundError",
+    "ConflictError",
+    "UnprocessableEntityError",
+    "RateLimitError",
+    "InternalServerError",
+    "Timeout",
+    "RequestOptions",
+    "Client",
+    "AsyncClient",
+    "Stream",
+    "AsyncStream",
+    "Gitpod",
+    "AsyncGitpod",
+    "file_from_path",
+    "BaseModel",
+    "DEFAULT_TIMEOUT",
+    "DEFAULT_MAX_RETRIES",
+    "DEFAULT_CONNECTION_LIMITS",
+    "DefaultHttpxClient",
+    "DefaultAsyncHttpxClient",
+    "DefaultAioHttpClient",
+]
+
+if not _t.TYPE_CHECKING:
+    from ._utils._resources_proxy import resources as resources
+
+_setup_logging()
+
+# Update the __module__ attribute for exported symbols so that
+# error messages point to this module instead of the module
+# it was originally defined in, e.g.
+# gitpod._exceptions.NotFoundError -> gitpod.NotFoundError
+__locals = locals()
+for __name in __all__:
+    if not __name.startswith("__"):
+        try:
+            __locals[__name].__module__ = "gitpod"
+        except (TypeError, AttributeError):
+            # Some of our exported symbols are builtins which we can't set attributes for.
+            pass
diff --git a/src/gitpod/_base_client.py b/src/gitpod/_base_client.py
new file mode 100644
index 0000000..550b73f
--- /dev/null
+++ b/src/gitpod/_base_client.py
@@ -0,0 +1,1992 @@
+from __future__ import annotations
+
+import sys
+import json
+import time
+import uuid
+import email
+import asyncio
+import inspect
+import logging
+import platform
+import email.utils
+from types import TracebackType
+from random import random
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Dict,
+    Type,
+    Union,
+    Generic,
+    Mapping,
+    TypeVar,
+    Iterable,
+    Iterator,
+    Optional,
+    Generator,
+    AsyncIterator,
+    cast,
+    overload,
+)
+from typing_extensions import Literal, override, get_origin
+
+import anyio
+import httpx
+import distro
+import pydantic
+from httpx import URL
+from pydantic import PrivateAttr
+
+from . import _exceptions
+from ._qs import Querystring
+from ._files import to_httpx_files, async_to_httpx_files
+from ._types import (
+    NOT_GIVEN,
+    Body,
+    Omit,
+    Query,
+    Headers,
+    Timeout,
+    NotGiven,
+    ResponseT,
+    AnyMapping,
+    PostParser,
+    RequestFiles,
+    HttpxSendArgs,
+    RequestOptions,
+    HttpxRequestFiles,
+    ModelBuilderProtocol,
+)
+from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
+from ._compat import PYDANTIC_V2, model_copy, model_dump
+from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
+from ._response import (
+    APIResponse,
+    BaseAPIResponse,
+    AsyncAPIResponse,
+    extract_response_type,
+)
+from ._constants import (
+    DEFAULT_TIMEOUT,
+    MAX_RETRY_DELAY,
+    DEFAULT_MAX_RETRIES,
+    INITIAL_RETRY_DELAY,
+    RAW_RESPONSE_HEADER,
+    OVERRIDE_CAST_TO_HEADER,
+    DEFAULT_CONNECTION_LIMITS,
+)
+from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder
+from ._exceptions import (
+    APIStatusError,
+    APITimeoutError,
+    APIConnectionError,
+    APIResponseValidationError,
+)
+
+log: logging.Logger = logging.getLogger(__name__)
+
+# TODO: make base page type vars covariant
+SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]")
+AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")
+
+
+_T = TypeVar("_T")
+_T_co = TypeVar("_T_co", covariant=True)
+
+_StreamT = TypeVar("_StreamT", bound=Stream[Any])
+_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])
+
+if TYPE_CHECKING:
+    from httpx._config import (
+        DEFAULT_TIMEOUT_CONFIG,  # pyright: ignore[reportPrivateImportUsage]
+    )
+
+    HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
+else:
+    try:
+        from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
+    except ImportError:
+        # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366
+        HTTPX_DEFAULT_TIMEOUT = Timeout(5.0)
+
+
+class PageInfo:
+    """Stores the necessary information to build the request to retrieve the next page.
+
+    Either `url` or `params` must be set.
+    """
+
+    url: URL | NotGiven
+    params: Query | NotGiven
+    json: Body | NotGiven
+
+    @overload
+    def __init__(
+        self,
+        *,
+        url: URL,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self,
+        *,
+        params: Query,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self,
+        *,
+        json: Body,
+    ) -> None: ...
+
+    def __init__(
+        self,
+        *,
+        url: URL | NotGiven = NOT_GIVEN,
+        json: Body | NotGiven = NOT_GIVEN,
+        params: Query | NotGiven = NOT_GIVEN,
+    ) -> None:
+        self.url = url
+        self.json = json
+        self.params = params
+
+    @override
+    def __repr__(self) -> str:
+        if self.url:
+            return f"{self.__class__.__name__}(url={self.url})"
+        if self.json:
+            return f"{self.__class__.__name__}(json={self.json})"
+        return f"{self.__class__.__name__}(params={self.params})"
+
+
+class BasePage(GenericModel, Generic[_T]):
+    """
+    Defines the core interface for pagination.
+
+    Type Args:
+        ModelT: The pydantic model that represents an item in the response.
+
+    Methods:
+        has_next_page(): Check if there is another page available
+        next_page_info(): Get the necessary information to make a request for the next page
+    """
+
+    _options: FinalRequestOptions = PrivateAttr()
+    _model: Type[_T] = PrivateAttr()
+
+    def has_next_page(self) -> bool:
+        items = self._get_page_items()
+        if not items:
+            return False
+        return self.next_page_info() is not None
+
+    def next_page_info(self) -> Optional[PageInfo]: ...
+
+    def _get_page_items(self) -> Iterable[_T]:  # type: ignore[empty-body]
+        ...
+
+    def _params_from_url(self, url: URL) -> httpx.QueryParams:
+        # TODO: do we have to preprocess params here?
+        return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params)
+
+    def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
+        options = model_copy(self._options)
+        options._strip_raw_response_header()
+
+        if not isinstance(info.params, NotGiven):
+            options.params = {**options.params, **info.params}
+            return options
+
+        if not isinstance(info.url, NotGiven):
+            params = self._params_from_url(info.url)
+            url = info.url.copy_with(params=params)
+            options.params = dict(url.params)
+            options.url = str(url)
+            return options
+
+        if not isinstance(info.json, NotGiven):
+            if not is_mapping(info.json):
+                raise TypeError("Pagination is only supported with mappings")
+
+            if not options.json_data:
+                options.json_data = {**info.json}
+            else:
+                if not is_mapping(options.json_data):
+                    raise TypeError("Pagination is only supported with mappings")
+
+                options.json_data = {**options.json_data, **info.json}
+            return options
+
+        raise ValueError("Unexpected PageInfo state")
+
+
+class BaseSyncPage(BasePage[_T], Generic[_T]):
+    _client: SyncAPIClient = pydantic.PrivateAttr()
+
+    def _set_private_attributes(
+        self,
+        client: SyncAPIClient,
+        model: Type[_T],
+        options: FinalRequestOptions,
+    ) -> None:
+        if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
+            self.__pydantic_private__ = {}
+
+        self._model = model
+        self._client = client
+        self._options = options
+
+    # Pydantic uses a custom `__iter__` method to support casting BaseModels
+    # to dictionaries. e.g. dict(model).
+    # As we want to support `for item in page`, this is inherently incompatible
+    # with the default pydantic behaviour. It is not possible to support both
+    # use cases at once. Fortunately, this is not a big deal as all other pydantic
+    # methods should continue to work as expected as there is an alternative method
+    # to cast a model to a dictionary, model.dict(), which is used internally
+    # by pydantic.
+    def __iter__(self) -> Iterator[_T]:  # type: ignore
+        for page in self.iter_pages():
+            for item in page._get_page_items():
+                yield item
+
+    def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]:
+        page = self
+        while True:
+            yield page
+            if page.has_next_page():
+                page = page.get_next_page()
+            else:
+                return
+
+    def get_next_page(self: SyncPageT) -> SyncPageT:
+        info = self.next_page_info()
+        if not info:
+            raise RuntimeError(
+                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
+            )
+
+        options = self._info_to_options(info)
+        return self._client._request_api_list(self._model, page=self.__class__, options=options)
+
+
+class AsyncPaginator(Generic[_T, AsyncPageT]):
+    def __init__(
+        self,
+        client: AsyncAPIClient,
+        options: FinalRequestOptions,
+        page_cls: Type[AsyncPageT],
+        model: Type[_T],
+    ) -> None:
+        self._model = model
+        self._client = client
+        self._options = options
+        self._page_cls = page_cls
+
+    def __await__(self) -> Generator[Any, None, AsyncPageT]:
+        return self._get_page().__await__()
+
+    async def _get_page(self) -> AsyncPageT:
+        def _parser(resp: AsyncPageT) -> AsyncPageT:
+            resp._set_private_attributes(
+                model=self._model,
+                options=self._options,
+                client=self._client,
+            )
+            return resp
+
+        self._options.post_parser = _parser
+
+        return await self._client.request(self._page_cls, self._options)
+
+    async def __aiter__(self) -> AsyncIterator[_T]:
+        # https://github.com/microsoft/pyright/issues/3464
+        page = cast(
+            AsyncPageT,
+            await self,  # type: ignore
+        )
+        async for item in page:
+            yield item
+
+
+class BaseAsyncPage(BasePage[_T], Generic[_T]):
+    _client: AsyncAPIClient = pydantic.PrivateAttr()
+
+    def _set_private_attributes(
+        self,
+        model: Type[_T],
+        client: AsyncAPIClient,
+        options: FinalRequestOptions,
+    ) -> None:
+        if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
+            self.__pydantic_private__ = {}
+
+        self._model = model
+        self._client = client
+        self._options = options
+
+    async def __aiter__(self) -> AsyncIterator[_T]:
+        async for page in self.iter_pages():
+            for item in page._get_page_items():
+                yield item
+
+    async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]:
+        page = self
+        while True:
+            yield page
+            if page.has_next_page():
+                page = await page.get_next_page()
+            else:
+                return
+
+    async def get_next_page(self: AsyncPageT) -> AsyncPageT:
+        info = self.next_page_info()
+        if not info:
+            raise RuntimeError(
+                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
+            )
+
+        options = self._info_to_options(info)
+        return await self._client._request_api_list(self._model, page=self.__class__, options=options)
+
+
+_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
+_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])
+
+
+class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
+    _client: _HttpxClientT
+    _version: str
+    _base_url: URL
+    max_retries: int
+    timeout: Union[float, Timeout, None]
+    _strict_response_validation: bool
+    _idempotency_header: str | None
+    _default_stream_cls: type[_DefaultStreamT] | None = None
+
+    def __init__(
+        self,
+        *,
+        version: str,
+        base_url: str | URL,
+        _strict_response_validation: bool,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        timeout: float | Timeout | None = DEFAULT_TIMEOUT,
+        custom_headers: Mapping[str, str] | None = None,
+        custom_query: Mapping[str, object] | None = None,
+    ) -> None:
+        self._version = version
+        self._base_url = self._enforce_trailing_slash(URL(base_url))
+        self.max_retries = max_retries
+        self.timeout = timeout
+        self._custom_headers = custom_headers or {}
+        self._custom_query = custom_query or {}
+        self._strict_response_validation = _strict_response_validation
+        self._idempotency_header = None
+        self._platform: Platform | None = None
+
+        if max_retries is None:  # pyright: ignore[reportUnnecessaryComparison]
+            raise TypeError(
+                "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `gitpod.DEFAULT_MAX_RETRIES`"
+            )
+
+    def _enforce_trailing_slash(self, url: URL) -> URL:
+        if url.raw_path.endswith(b"/"):
+            return url
+        return url.copy_with(raw_path=url.raw_path + b"/")
+
+    def _make_status_error_from_response(
+        self,
+        response: httpx.Response,
+    ) -> APIStatusError:
+        if response.is_closed and not response.is_stream_consumed:
+            # We can't read the response body as it has been closed
+            # before it was read. This can happen if an event hook
+            # raises a status error.
+            body = None
+            err_msg = f"Error code: {response.status_code}"
+        else:
+            err_text = response.text.strip()
+            body = err_text
+
+            try:
+                body = json.loads(err_text)
+                err_msg = f"Error code: {response.status_code} - {body}"
+            except Exception:
+                err_msg = err_text or f"Error code: {response.status_code}"
+
+        return self._make_status_error(err_msg, body=body, response=response)
+
+    def _make_status_error(
+        self,
+        err_msg: str,
+        *,
+        body: object,
+        response: httpx.Response,
+    ) -> _exceptions.APIStatusError:
+        raise NotImplementedError()
+
+    def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
+        custom_headers = options.headers or {}
+        headers_dict = _merge_mappings(self.default_headers, custom_headers)
+        self._validate_headers(headers_dict, custom_headers)
+
+        # headers are case-insensitive while dictionaries are not.
+        headers = httpx.Headers(headers_dict)
+
+        idempotency_header = self._idempotency_header
+        if idempotency_header and options.idempotency_key and idempotency_header not in headers:
+            headers[idempotency_header] = options.idempotency_key
+
+        # Don't set these headers if they were already set or removed by the caller. We check
+        # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
+        lower_custom_headers = [header.lower() for header in custom_headers]
+        if "x-stainless-retry-count" not in lower_custom_headers:
+            headers["x-stainless-retry-count"] = str(retries_taken)
+        if "x-stainless-read-timeout" not in lower_custom_headers:
+            timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
+            if isinstance(timeout, Timeout):
+                timeout = timeout.read
+            if timeout is not None:
+                headers["x-stainless-read-timeout"] = str(timeout)
+
+        return headers
+
+    def _prepare_url(self, url: str) -> URL:
+        """
+        Merge a URL argument together with any 'base_url' on the client,
+        to create the URL used for the outgoing request.
+        """
+        # Copied from httpx's `_merge_url` method.
+        merge_url = URL(url)
+        if merge_url.is_relative_url:
+            merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
+            return self.base_url.copy_with(raw_path=merge_raw_path)
+
+        return merge_url
+
+    def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
+        return SSEDecoder()
+
+    def _build_request(
+        self,
+        options: FinalRequestOptions,
+        *,
+        retries_taken: int = 0,
+    ) -> httpx.Request:
+        if log.isEnabledFor(logging.DEBUG):
+            log.debug("Request options: %s", model_dump(options, exclude_unset=True))
+
+        kwargs: dict[str, Any] = {}
+
+        json_data = options.json_data
+        if options.extra_json is not None:
+            if json_data is None:
+                json_data = cast(Body, options.extra_json)
+            elif is_mapping(json_data):
+                json_data = _merge_mappings(json_data, options.extra_json)
+            else:
+                raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")
+
+        headers = self._build_headers(options, retries_taken=retries_taken)
+        params = _merge_mappings(self.default_query, options.params)
+        content_type = headers.get("Content-Type")
+        files = options.files
+
+        # If the given Content-Type header is multipart/form-data then it
+        # has to be removed so that httpx can generate the header with
+        # additional information for us as it has to be in this form
+        # for the server to be able to correctly parse the request:
+        # multipart/form-data; boundary=---abc--
+        if content_type is not None and content_type.startswith("multipart/form-data"):
+            if "boundary" not in content_type:
+                # only remove the header if the boundary hasn't been explicitly set
+                # as the caller doesn't want httpx to come up with their own boundary
+                headers.pop("Content-Type")
+
+            # As we are now sending multipart/form-data instead of application/json
+            # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
+            if json_data:
+                if not is_dict(json_data):
+                    raise TypeError(
+                        f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead."
+                    )
+                kwargs["data"] = self._serialize_multipartform(json_data)
+
+            # httpx determines whether or not to send a "multipart/form-data"
+            # request based on the truthiness of the "files" argument.
+            # This gets around that issue by generating a dict value that
+            # evaluates to true.
+            #
+            # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186
+            if not files:
+                files = cast(HttpxRequestFiles, ForceMultipartDict())
+
+        prepared_url = self._prepare_url(options.url)
+        if "_" in prepared_url.host:
+            # work around https://github.com/encode/httpx/discussions/2880
+            kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
+
+        is_body_allowed = options.method.lower() != "get"
+
+        if is_body_allowed:
+            kwargs["json"] = json_data if is_given(json_data) else None
+            kwargs["files"] = files
+        else:
+            headers.pop("Content-Type", None)
+            kwargs.pop("data", None)
+
+        # TODO: report this error to httpx
+        return self._client.build_request(  # pyright: ignore[reportUnknownMemberType]
+            headers=headers,
+            timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
+            method=options.method,
+            url=prepared_url,
+            # the `Query` type that we use is incompatible with qs'
+            # `Params` type as it needs to be typed as `Mapping[str, object]`
+            # so that passing a `TypedDict` doesn't cause an error.
+            # https://github.com/microsoft/pyright/issues/3526#event-6715453066
+            params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
+            **kwargs,
+        )
+
+    def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]:
+        items = self.qs.stringify_items(
+            # TODO: type ignore is required as stringify_items is well typed but we can't be
+            # well typed without heavy validation.
+            data,  # type: ignore
+            array_format="brackets",
+        )
+        serialized: dict[str, object] = {}
+        for key, value in items:
+            existing = serialized.get(key)
+
+            if not existing:
+                serialized[key] = value
+                continue
+
+            # If a value has already been set for this key then that
+            # means we're sending data like `array[]=[1, 2, 3]` and we
+            # need to tell httpx that we want to send multiple values with
+            # the same key which is done by using a list or a tuple.
+            #
+            # Note: 2d arrays should never result in the same key at both
+            # levels so it's safe to assume that if the value is a list,
+            # it was because we changed it to be a list.
+            if is_list(existing):
+                existing.append(value)
+            else:
+                serialized[key] = [existing, value]
+
+        return serialized
+
+    def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]:
+        if not is_given(options.headers):
+            return cast_to
+
+        # make a copy of the headers so we don't mutate user-input
+        headers = dict(options.headers)
+
+        # we internally support defining a temporary header to override the
+        # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response`
+        # see _response.py for implementation details
+        override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN)
+        if is_given(override_cast_to):
+            options.headers = headers
+            return cast(Type[ResponseT], override_cast_to)
+
+        return cast_to
+
+    def _should_stream_response_body(self, request: httpx.Request) -> bool:
+        return request.headers.get(RAW_RESPONSE_HEADER) == "stream"  # type: ignore[no-any-return]
+
+    def _process_response_data(
+        self,
+        *,
+        data: object,
+        cast_to: type[ResponseT],
+        response: httpx.Response,
+    ) -> ResponseT:
+        if data is None:
+            return cast(ResponseT, None)
+
+        if cast_to is object:
+            return cast(ResponseT, data)
+
+        try:
+            if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol):
+                return cast(ResponseT, cast_to.build(response=response, data=data))
+
+            if self._strict_response_validation:
+                return cast(ResponseT, validate_type(type_=cast_to, value=data))
+
+            return cast(ResponseT, construct_type(type_=cast_to, value=data))
+        except pydantic.ValidationError as err:
+            raise APIResponseValidationError(response=response, body=data) from err
+
+    @property
+    def qs(self) -> Querystring:
+        return Querystring()
+
+    @property
+    def custom_auth(self) -> httpx.Auth | None:
+        return None
+
+    @property
+    def auth_headers(self) -> dict[str, str]:
+        return {}
+
+    @property
+    def default_headers(self) -> dict[str, str | Omit]:
+        return {
+            "Accept": "application/json",
+            "Content-Type": "application/json",
+            "User-Agent": self.user_agent,
+            **self.platform_headers(),
+            **self.auth_headers,
+            **self._custom_headers,
+        }
+
+    @property
+    def default_query(self) -> dict[str, object]:
+        return {
+            **self._custom_query,
+        }
+
+    def _validate_headers(
+        self,
+        headers: Headers,  # noqa: ARG002
+        custom_headers: Headers,  # noqa: ARG002
+    ) -> None:
+        """Validate the given default headers and custom headers.
+
+        Does nothing by default.
+        """
+        return
+
+    @property
+    def user_agent(self) -> str:
+        return f"{self.__class__.__name__}/Python {self._version}"
+
+    @property
+    def base_url(self) -> URL:
+        return self._base_url
+
+    @base_url.setter
+    def base_url(self, url: URL | str) -> None:
+        self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))
+
+    def platform_headers(self) -> Dict[str, str]:
+        # the actual implementation is in a separate `lru_cache` decorated
+        # function because adding `lru_cache` to methods will leak memory
+        # https://github.com/python/cpython/issues/88476
+        return platform_headers(self._version, platform=self._platform)
+
+    def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
+        """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.
+
+        About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
+        See also  https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax
+        """
+        if response_headers is None:
+            return None
+
+        # First, try the non-standard `retry-after-ms` header for milliseconds,
+        # which is more precise than integer-seconds `retry-after`
+        try:
+            retry_ms_header = response_headers.get("retry-after-ms", None)
+            return float(retry_ms_header) / 1000
+        except (TypeError, ValueError):
+            pass
+
+        # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats).
+        retry_header = response_headers.get("retry-after")
+        try:
+            # note: the spec indicates that this should only ever be an integer
+            # but if someone sends a float there's no reason for us to not respect it
+            return float(retry_header)
+        except (TypeError, ValueError):
+            pass
+
+        # Last, try parsing `retry-after` as a date.
+        retry_date_tuple = email.utils.parsedate_tz(retry_header)
+        if retry_date_tuple is None:
+            return None
+
+        retry_date = email.utils.mktime_tz(retry_date_tuple)
+        return float(retry_date - time.time())
+
+    def _calculate_retry_timeout(
+        self,
+        remaining_retries: int,
+        options: FinalRequestOptions,
+        response_headers: Optional[httpx.Headers] = None,
+    ) -> float:
+        max_retries = options.get_max_retries(self.max_retries)
+
+        # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
+        retry_after = self._parse_retry_after_header(response_headers)
+        if retry_after is not None and 0 < retry_after <= 60:
+            return retry_after
+
+        # Also cap retry count to 1000 to avoid any potential overflows with `pow`
+        nb_retries = min(max_retries - remaining_retries, 1000)
+
+        # Apply exponential backoff, but not more than the max.
+        sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)
+
+        # Apply some jitter, plus-or-minus half a second.
+        jitter = 1 - 0.25 * random()
+        timeout = sleep_seconds * jitter
+        return timeout if timeout >= 0 else 0
+
+    def _should_retry(self, response: httpx.Response) -> bool:
+        # Note: this is not a standard header
+        should_retry_header = response.headers.get("x-should-retry")
+
+        # If the server explicitly says whether or not to retry, obey.
+        if should_retry_header == "true":
+            log.debug("Retrying as header `x-should-retry` is set to `true`")
+            return True
+        if should_retry_header == "false":
+            log.debug("Not retrying as header `x-should-retry` is set to `false`")
+            return False
+
+        # Retry on request timeouts.
+        if response.status_code == 408:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        # Retry on lock timeouts.
+        if response.status_code == 409:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        # Retry on rate limits.
+        if response.status_code == 429:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        # Retry internal errors.
+        if response.status_code >= 500:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        log.debug("Not retrying")
+        return False
+
+    def _idempotency_key(self) -> str:
+        return f"stainless-python-retry-{uuid.uuid4()}"
+
+
+class _DefaultHttpxClient(httpx.Client):
+    def __init__(self, **kwargs: Any) -> None:
+        kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+        kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+        kwargs.setdefault("follow_redirects", True)
+        super().__init__(**kwargs)
+
+
+if TYPE_CHECKING:
+    DefaultHttpxClient = httpx.Client
+    """An alias to `httpx.Client` that provides the same defaults that this SDK
+    uses internally.
+
+    This is useful because overriding the `http_client` with your own instance of
+    `httpx.Client` will result in httpx's defaults being used, not ours.
+    """
+else:
+    DefaultHttpxClient = _DefaultHttpxClient
+
+
+class SyncHttpxClientWrapper(DefaultHttpxClient):
+    def __del__(self) -> None:
+        if self.is_closed:
+            return
+
+        try:
+            self.close()
+        except Exception:
+            pass
+
+
+class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
+    _client: httpx.Client
+    _default_stream_cls: type[Stream[Any]] | None = None
+
+    def __init__(
+        self,
+        *,
+        version: str,
+        base_url: str | URL,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        http_client: httpx.Client | None = None,
+        custom_headers: Mapping[str, str] | None = None,
+        custom_query: Mapping[str, object] | None = None,
+        _strict_response_validation: bool,
+    ) -> None:
+        if not is_given(timeout):
+            # if the user passed in a custom http client with a non-default
+            # timeout set then we use that timeout.
+            #
+            # note: there is an edge case here where the user passes in a client
+            # where they've explicitly set the timeout to match the default timeout
+            # as this check is structural, meaning that we'll think they didn't
+            # pass in a timeout and will ignore it
+            if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT:
+                timeout = http_client.timeout
+            else:
+                timeout = DEFAULT_TIMEOUT
+
+        if http_client is not None and not isinstance(http_client, httpx.Client):  # pyright: ignore[reportUnnecessaryIsInstance]
+            raise TypeError(
+                f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}"
+            )
+
+        super().__init__(
+            version=version,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+            base_url=base_url,
+            max_retries=max_retries,
+            custom_query=custom_query,
+            custom_headers=custom_headers,
+            _strict_response_validation=_strict_response_validation,
+        )
+        self._client = http_client or SyncHttpxClientWrapper(
+            base_url=base_url,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+        )
+
+    def is_closed(self) -> bool:
+        return self._client.is_closed
+
+    def close(self) -> None:
+        """Close the underlying HTTPX client.
+
+        The client will *not* be usable after this.
+        """
+        # If an error is thrown while constructing a client, self._client
+        # may not be present
+        if hasattr(self, "_client"):
+            self._client.close()
+
+    def __enter__(self: _T) -> _T:
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.close()
+
+    def _prepare_options(
+        self,
+        options: FinalRequestOptions,  # noqa: ARG002
+    ) -> FinalRequestOptions:
+        """Hook for mutating the given options"""
+        return options
+
+    def _prepare_request(
+        self,
+        request: httpx.Request,  # noqa: ARG002
+    ) -> None:
+        """This method is used as a callback for mutating the `Request` object
+        after it has been constructed.
+        This is useful for cases where you want to add certain headers based off of
+        the request properties, e.g. `url`, `method` etc.
+        """
+        return None
+
+    @overload
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: Literal[True],
+        stream_cls: Type[_StreamT],
+    ) -> _StreamT: ...
+
+    @overload
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: bool = False,
+        stream_cls: Type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT: ...
+
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: bool = False,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT:
+        cast_to = self._maybe_override_cast_to(cast_to, options)
+
+        # create a copy of the options we were given so that if the
+        # options are mutated later & we then retry, the retries are
+        # given the original options
+        input_options = model_copy(options)
+        if input_options.idempotency_key is None and input_options.method.lower() != "get":
+            # ensure the idempotency key is reused between requests
+            input_options.idempotency_key = self._idempotency_key()
+
+        response: httpx.Response | None = None
+        max_retries = input_options.get_max_retries(self.max_retries)
+
+        retries_taken = 0
+        for retries_taken in range(max_retries + 1):
+            options = model_copy(input_options)
+            options = self._prepare_options(options)
+
+            remaining_retries = max_retries - retries_taken
+            request = self._build_request(options, retries_taken=retries_taken)
+            self._prepare_request(request)
+
+            kwargs: HttpxSendArgs = {}
+            if self.custom_auth is not None:
+                kwargs["auth"] = self.custom_auth
+
+            if options.follow_redirects is not None:
+                kwargs["follow_redirects"] = options.follow_redirects
+
+            log.debug("Sending HTTP Request: %s %s", request.method, request.url)
+
+            response = None
+            try:
+                response = self._client.send(
+                    request,
+                    stream=stream or self._should_stream_response_body(request=request),
+                    **kwargs,
+                )
+            except httpx.TimeoutException as err:
+                log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+                if remaining_retries > 0:
+                    self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising timeout error")
+                raise APITimeoutError(request=request) from err
+            except Exception as err:
+                log.debug("Encountered Exception", exc_info=True)
+
+                if remaining_retries > 0:
+                    self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising connection error")
+                raise APIConnectionError(request=request) from err
+
+            log.debug(
+                'HTTP Response: %s %s "%i %s" %s',
+                request.method,
+                request.url,
+                response.status_code,
+                response.reason_phrase,
+                response.headers,
+            )
+
+            try:
+                response.raise_for_status()
+            except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
+                log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+                if remaining_retries > 0 and self._should_retry(err.response):
+                    err.response.close()
+                    self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=response,
+                    )
+                    continue
+
+                # If the response is streamed then we need to explicitly read the response
+                # to completion before attempting to access the response text.
+                if not err.response.is_closed:
+                    err.response.read()
+
+                log.debug("Re-raising status error")
+                raise self._make_status_error_from_response(err.response) from None
+
+            break
+
+        assert response is not None, "could not resolve response (should never happen)"
+        return self._process_response(
+            cast_to=cast_to,
+            options=options,
+            response=response,
+            stream=stream,
+            stream_cls=stream_cls,
+            retries_taken=retries_taken,
+        )
+
+    def _sleep_for_retry(
+        self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+    ) -> None:
+        remaining_retries = max_retries - retries_taken
+        if remaining_retries == 1:
+            log.debug("1 retry left")
+        else:
+            log.debug("%i retries left", remaining_retries)
+
+        timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
+        log.info("Retrying request to %s in %f seconds", options.url, timeout)
+
+        time.sleep(timeout)
+
+    def _process_response(
+        self,
+        *,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        response: httpx.Response,
+        stream: bool,
+        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+        retries_taken: int = 0,
+    ) -> ResponseT:
+        origin = get_origin(cast_to) or cast_to
+
+        if (
+            inspect.isclass(origin)
+            and issubclass(origin, BaseAPIResponse)
+            # we only want to actually return the custom BaseAPIResponse class if we're
+            # returning the raw response, or if we're not streaming SSE, as if we're streaming
+            # SSE then `cast_to` doesn't actively reflect the type we need to parse into
+            and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
+        ):
+            if not issubclass(origin, APIResponse):
+                raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
+
+            response_cls = cast("type[BaseAPIResponse[Any]]", cast_to)
+            return cast(
+                ResponseT,
+                response_cls(
+                    raw=response,
+                    client=self,
+                    cast_to=extract_response_type(response_cls),
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    options=options,
+                    retries_taken=retries_taken,
+                ),
+            )
+
+        if cast_to == httpx.Response:
+            return cast(ResponseT, response)
+
+        api_response = APIResponse(
+            raw=response,
+            client=self,
+            cast_to=cast("type[ResponseT]", cast_to),  # pyright: ignore[reportUnnecessaryCast]
+            stream=stream,
+            stream_cls=stream_cls,
+            options=options,
+            retries_taken=retries_taken,
+        )
+        if bool(response.request.headers.get(RAW_RESPONSE_HEADER)):
+            return cast(ResponseT, api_response)
+
+        return api_response.parse()
+
+    def _request_api_list(
+        self,
+        model: Type[object],
+        page: Type[SyncPageT],
+        options: FinalRequestOptions,
+    ) -> SyncPageT:
+        def _parser(resp: SyncPageT) -> SyncPageT:
+            resp._set_private_attributes(
+                client=self,
+                model=model,
+                options=options,
+            )
+            return resp
+
+        options.post_parser = _parser
+
+        return self.request(page, options, stream=False)
+
+    @overload
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[True],
+        stream_cls: type[_StreamT],
+    ) -> _StreamT: ...
+
+    @overload
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT: ...
+
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool = False,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT:
+        opts = FinalRequestOptions.construct(method="get", url=path, **options)
+        # cast is required because mypy complains about returning Any even though
+        # it understands the type variables
+        return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
+
+    @overload
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: Literal[True],
+        stream_cls: type[_StreamT],
+    ) -> _StreamT: ...
+
+    @overload
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: bool,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT: ...
+
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: bool = False,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT:
+        opts = FinalRequestOptions.construct(
+            method="post", url=path, json_data=body, files=to_httpx_files(files), **options
+        )
+        return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
+
+    def patch(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+        return self.request(cast_to, opts)
+
+    def put(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(
+            method="put", url=path, json_data=body, files=to_httpx_files(files), **options
+        )
+        return self.request(cast_to, opts)
+
+    def delete(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+        return self.request(cast_to, opts)
+
+    def get_api_list(
+        self,
+        path: str,
+        *,
+        model: Type[object],
+        page: Type[SyncPageT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        method: str = "get",
+    ) -> SyncPageT:
+        opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options)
+        return self._request_api_list(model, page, opts)
+
+
+class _DefaultAsyncHttpxClient(httpx.AsyncClient):
+    def __init__(self, **kwargs: Any) -> None:
+        kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+        kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+        kwargs.setdefault("follow_redirects", True)
+        super().__init__(**kwargs)
+
+
+try:
+    import httpx_aiohttp
+except ImportError:
+
+    class _DefaultAioHttpClient(httpx.AsyncClient):
+        def __init__(self, **_kwargs: Any) -> None:
+            raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
+else:
+
+    class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient):  # type: ignore
+        def __init__(self, **kwargs: Any) -> None:
+            kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+            kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+            kwargs.setdefault("follow_redirects", True)
+
+            super().__init__(**kwargs)
+
+
+if TYPE_CHECKING:
+    DefaultAsyncHttpxClient = httpx.AsyncClient
+    """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
+    uses internally.
+
+    This is useful because overriding the `http_client` with your own instance of
+    `httpx.AsyncClient` will result in httpx's defaults being used, not ours.
+    """
+
+    DefaultAioHttpClient = httpx.AsyncClient
+    """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
+else:
+    DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
+    DefaultAioHttpClient = _DefaultAioHttpClient
+
+
+class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
+    def __del__(self) -> None:
+        if self.is_closed:
+            return
+
+        try:
+            # TODO(someday): support non asyncio runtimes here
+            asyncio.get_running_loop().create_task(self.aclose())
+        except Exception:
+            pass
+
+
+class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
+    _client: httpx.AsyncClient
+    _default_stream_cls: type[AsyncStream[Any]] | None = None
+
+    def __init__(
+        self,
+        *,
+        version: str,
+        base_url: str | URL,
+        _strict_response_validation: bool,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        http_client: httpx.AsyncClient | None = None,
+        custom_headers: Mapping[str, str] | None = None,
+        custom_query: Mapping[str, object] | None = None,
+    ) -> None:
+        if not is_given(timeout):
+            # if the user passed in a custom http client with a non-default
+            # timeout set then we use that timeout.
+            #
+            # note: there is an edge case here where the user passes in a client
+            # where they've explicitly set the timeout to match the default timeout
+            # as this check is structural, meaning that we'll think they didn't
+            # pass in a timeout and will ignore it
+            if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT:
+                timeout = http_client.timeout
+            else:
+                timeout = DEFAULT_TIMEOUT
+
+        if http_client is not None and not isinstance(http_client, httpx.AsyncClient):  # pyright: ignore[reportUnnecessaryIsInstance]
+            raise TypeError(
+                f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}"
+            )
+
+        super().__init__(
+            version=version,
+            base_url=base_url,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+            max_retries=max_retries,
+            custom_query=custom_query,
+            custom_headers=custom_headers,
+            _strict_response_validation=_strict_response_validation,
+        )
+        self._client = http_client or AsyncHttpxClientWrapper(
+            base_url=base_url,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+        )
+
+    def is_closed(self) -> bool:
+        return self._client.is_closed
+
+    async def close(self) -> None:
+        """Close the underlying HTTPX client.
+
+        The client will *not* be usable after this.
+        """
+        await self._client.aclose()
+
+    async def __aenter__(self: _T) -> _T:
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        await self.close()
+
+    async def _prepare_options(
+        self,
+        options: FinalRequestOptions,  # noqa: ARG002
+    ) -> FinalRequestOptions:
+        """Hook for mutating the given options"""
+        return options
+
+    async def _prepare_request(
+        self,
+        request: httpx.Request,  # noqa: ARG002
+    ) -> None:
+        """This method is used as a callback for mutating the `Request` object
+        after it has been constructed.
+        This is useful for cases where you want to add certain headers based off of
+        the request properties, e.g. `url`, `method` etc.
+        """
+        return None
+
+    @overload
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: Literal[True],
+        stream_cls: type[_AsyncStreamT],
+    ) -> _AsyncStreamT: ...
+
+    @overload
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT: ...
+
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: bool = False,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT:
+        if self._platform is None:
+            # `get_platform` can make blocking IO calls so we
+            # execute it earlier while we are in an async context
+            self._platform = await asyncify(get_platform)()
+
+        cast_to = self._maybe_override_cast_to(cast_to, options)
+
+        # create a copy of the options we were given so that if the
+        # options are mutated later & we then retry, the retries are
+        # given the original options
+        input_options = model_copy(options)
+        if input_options.idempotency_key is None and input_options.method.lower() != "get":
+            # ensure the idempotency key is reused between requests
+            input_options.idempotency_key = self._idempotency_key()
+
+        response: httpx.Response | None = None
+        max_retries = input_options.get_max_retries(self.max_retries)
+
+        retries_taken = 0
+        for retries_taken in range(max_retries + 1):
+            options = model_copy(input_options)
+            options = await self._prepare_options(options)
+
+            remaining_retries = max_retries - retries_taken
+            request = self._build_request(options, retries_taken=retries_taken)
+            await self._prepare_request(request)
+
+            kwargs: HttpxSendArgs = {}
+            if self.custom_auth is not None:
+                kwargs["auth"] = self.custom_auth
+
+            if options.follow_redirects is not None:
+                kwargs["follow_redirects"] = options.follow_redirects
+
+            log.debug("Sending HTTP Request: %s %s", request.method, request.url)
+
+            response = None
+            try:
+                response = await self._client.send(
+                    request,
+                    stream=stream or self._should_stream_response_body(request=request),
+                    **kwargs,
+                )
+            except httpx.TimeoutException as err:
+                log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+                if remaining_retries > 0:
+                    await self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising timeout error")
+                raise APITimeoutError(request=request) from err
+            except Exception as err:
+                log.debug("Encountered Exception", exc_info=True)
+
+                if remaining_retries > 0:
+                    await self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising connection error")
+                raise APIConnectionError(request=request) from err
+
+            log.debug(
+                'HTTP Response: %s %s "%i %s" %s',
+                request.method,
+                request.url,
+                response.status_code,
+                response.reason_phrase,
+                response.headers,
+            )
+
+            try:
+                response.raise_for_status()
+            except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
+                log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+                if remaining_retries > 0 and self._should_retry(err.response):
+                    await err.response.aclose()
+                    await self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=response,
+                    )
+                    continue
+
+                # If the response is streamed then we need to explicitly read the response
+                # to completion before attempting to access the response text.
+                if not err.response.is_closed:
+                    await err.response.aread()
+
+                log.debug("Re-raising status error")
+                raise self._make_status_error_from_response(err.response) from None
+
+            break
+
+        assert response is not None, "could not resolve response (should never happen)"
+        return await self._process_response(
+            cast_to=cast_to,
+            options=options,
+            response=response,
+            stream=stream,
+            stream_cls=stream_cls,
+            retries_taken=retries_taken,
+        )
+
+    async def _sleep_for_retry(
+        self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+    ) -> None:
+        remaining_retries = max_retries - retries_taken
+        if remaining_retries == 1:
+            log.debug("1 retry left")
+        else:
+            log.debug("%i retries left", remaining_retries)
+
+        timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
+        log.info("Retrying request to %s in %f seconds", options.url, timeout)
+
+        await anyio.sleep(timeout)
+
+    async def _process_response(
+        self,
+        *,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        response: httpx.Response,
+        stream: bool,
+        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+        retries_taken: int = 0,
+    ) -> ResponseT:
+        origin = get_origin(cast_to) or cast_to
+
+        if (
+            inspect.isclass(origin)
+            and issubclass(origin, BaseAPIResponse)
+            # we only want to actually return the custom BaseAPIResponse class if we're
+            # returning the raw response, or if we're not streaming SSE, as if we're streaming
+            # SSE then `cast_to` doesn't actively reflect the type we need to parse into
+            and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
+        ):
+            if not issubclass(origin, AsyncAPIResponse):
+                raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
+
+            response_cls = cast("type[BaseAPIResponse[Any]]", cast_to)
+            return cast(
+                "ResponseT",
+                response_cls(
+                    raw=response,
+                    client=self,
+                    cast_to=extract_response_type(response_cls),
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    options=options,
+                    retries_taken=retries_taken,
+                ),
+            )
+
+        if cast_to == httpx.Response:
+            return cast(ResponseT, response)
+
+        api_response = AsyncAPIResponse(
+            raw=response,
+            client=self,
+            cast_to=cast("type[ResponseT]", cast_to),  # pyright: ignore[reportUnnecessaryCast]
+            stream=stream,
+            stream_cls=stream_cls,
+            options=options,
+            retries_taken=retries_taken,
+        )
+        if bool(response.request.headers.get(RAW_RESPONSE_HEADER)):
+            return cast(ResponseT, api_response)
+
+        return await api_response.parse()
+
+    def _request_api_list(
+        self,
+        model: Type[_T],
+        page: Type[AsyncPageT],
+        options: FinalRequestOptions,
+    ) -> AsyncPaginator[_T, AsyncPageT]:
+        return AsyncPaginator(client=self, options=options, page_cls=page, model=model)
+
+    @overload
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[True],
+        stream_cls: type[_AsyncStreamT],
+    ) -> _AsyncStreamT: ...
+
+    @overload
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT: ...
+
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool = False,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT:
+        opts = FinalRequestOptions.construct(method="get", url=path, **options)
+        return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
+
+    @overload
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: Literal[True],
+        stream_cls: type[_AsyncStreamT],
+    ) -> _AsyncStreamT: ...
+
+    @overload
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT: ...
+
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: bool = False,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT:
+        opts = FinalRequestOptions.construct(
+            method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+        )
+        return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
+
+    async def patch(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+        return await self.request(cast_to, opts)
+
+    async def put(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(
+            method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+        )
+        return await self.request(cast_to, opts)
+
+    async def delete(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+        return await self.request(cast_to, opts)
+
+    def get_api_list(
+        self,
+        path: str,
+        *,
+        model: Type[_T],
+        page: Type[AsyncPageT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        method: str = "get",
+    ) -> AsyncPaginator[_T, AsyncPageT]:
+        opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options)
+        return self._request_api_list(model, page, opts)
+
+
+def make_request_options(
+    *,
+    query: Query | None = None,
+    extra_headers: Headers | None = None,
+    extra_query: Query | None = None,
+    extra_body: Body | None = None,
+    idempotency_key: str | None = None,
+    timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    post_parser: PostParser | NotGiven = NOT_GIVEN,
+) -> RequestOptions:
+    """Create a dict of type RequestOptions without keys of NotGiven values."""
+    options: RequestOptions = {}
+    if extra_headers is not None:
+        options["headers"] = extra_headers
+
+    if extra_body is not None:
+        options["extra_json"] = cast(AnyMapping, extra_body)
+
+    if query is not None:
+        options["params"] = query
+
+    if extra_query is not None:
+        options["params"] = {**options.get("params", {}), **extra_query}
+
+    if not isinstance(timeout, NotGiven):
+        options["timeout"] = timeout
+
+    if idempotency_key is not None:
+        options["idempotency_key"] = idempotency_key
+
+    if is_given(post_parser):
+        # internal
+        options["post_parser"] = post_parser  # type: ignore
+
+    return options
+
+
+class ForceMultipartDict(Dict[str, None]):
+    def __bool__(self) -> bool:
+        return True
+
+
+class OtherPlatform:
+    def __init__(self, name: str) -> None:
+        self.name = name
+
+    @override
+    def __str__(self) -> str:
+        return f"Other:{self.name}"
+
+
+Platform = Union[
+    OtherPlatform,
+    Literal[
+        "MacOS",
+        "Linux",
+        "Windows",
+        "FreeBSD",
+        "OpenBSD",
+        "iOS",
+        "Android",
+        "Unknown",
+    ],
+]
+
+
+def get_platform() -> Platform:
+    try:
+        system = platform.system().lower()
+        platform_name = platform.platform().lower()
+    except Exception:
+        return "Unknown"
+
+    if "iphone" in platform_name or "ipad" in platform_name:
+        # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7
+        # system is Darwin and platform_name is a string like:
+        # - Darwin-21.6.0-iPhone12,1-64bit
+        # - Darwin-21.6.0-iPad7,11-64bit
+        return "iOS"
+
+    if system == "darwin":
+        return "MacOS"
+
+    if system == "windows":
+        return "Windows"
+
+    if "android" in platform_name:
+        # Tested using Pydroid 3
+        # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc'
+        return "Android"
+
+    if system == "linux":
+        # https://distro.readthedocs.io/en/latest/#distro.id
+        distro_id = distro.id()
+        if distro_id == "freebsd":
+            return "FreeBSD"
+
+        if distro_id == "openbsd":
+            return "OpenBSD"
+
+        return "Linux"
+
+    if platform_name:
+        return OtherPlatform(platform_name)
+
+    return "Unknown"
+
+
+@lru_cache(maxsize=None)
+def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]:
+    return {
+        "X-Stainless-Lang": "python",
+        "X-Stainless-Package-Version": version,
+        "X-Stainless-OS": str(platform or get_platform()),
+        "X-Stainless-Arch": str(get_architecture()),
+        "X-Stainless-Runtime": get_python_runtime(),
+        "X-Stainless-Runtime-Version": get_python_version(),
+    }
+
+
+class OtherArch:
+    def __init__(self, name: str) -> None:
+        self.name = name
+
+    @override
+    def __str__(self) -> str:
+        return f"other:{self.name}"
+
+
+Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]]
+
+
+def get_python_runtime() -> str:
+    try:
+        return platform.python_implementation()
+    except Exception:
+        return "unknown"
+
+
+def get_python_version() -> str:
+    try:
+        return platform.python_version()
+    except Exception:
+        return "unknown"
+
+
+def get_architecture() -> Arch:
+    try:
+        machine = platform.machine().lower()
+    except Exception:
+        return "unknown"
+
+    if machine in ("arm64", "aarch64"):
+        return "arm64"
+
+    # TODO: untested
+    if machine == "arm":
+        return "arm"
+
+    if machine == "x86_64":
+        return "x64"
+
+    # TODO: untested
+    if sys.maxsize <= 2**32:
+        return "x32"
+
+    if machine:
+        return OtherArch(machine)
+
+    return "unknown"
+
+
+def _merge_mappings(
+    obj1: Mapping[_T_co, Union[_T, Omit]],
+    obj2: Mapping[_T_co, Union[_T, Omit]],
+) -> Dict[_T_co, _T]:
+    """Merge two mappings of the same type, removing any values that are instances of `Omit`.
+
+    In cases with duplicate keys the second mapping takes precedence.
+    """
+    merged = {**obj1, **obj2}
+    return {key: value for key, value in merged.items() if not isinstance(value, Omit)}
diff --git a/src/gitpod/_client.py b/src/gitpod/_client.py
new file mode 100644
index 0000000..dd0ccc2
--- /dev/null
+++ b/src/gitpod/_client.py
@@ -0,0 +1,495 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Union, Mapping
+from typing_extensions import Self, override
+
+import httpx
+
+from . import _exceptions
+from ._qs import Querystring
+from ._types import (
+    NOT_GIVEN,
+    Omit,
+    Timeout,
+    NotGiven,
+    Transport,
+    ProxiesTypes,
+    RequestOptions,
+)
+from ._utils import is_given, get_async_library
+from ._version import __version__
+from .resources import usage, events, groups, editors, secrets, accounts, gateways, identity
+from ._streaming import Stream as Stream, AsyncStream as AsyncStream
+from ._exceptions import GitpodError, APIStatusError
+from ._base_client import (
+    DEFAULT_MAX_RETRIES,
+    SyncAPIClient,
+    AsyncAPIClient,
+)
+from .resources.users import users
+from .resources.runners import runners
+from .resources.projects import projects
+from .resources.environments import environments
+from .resources.organizations import organizations
+
+__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Gitpod", "AsyncGitpod", "Client", "AsyncClient"]
+
+
+class Gitpod(SyncAPIClient):
+    accounts: accounts.AccountsResource
+    editors: editors.EditorsResource
+    environments: environments.EnvironmentsResource
+    events: events.EventsResource
+    gateways: gateways.GatewaysResource
+    groups: groups.GroupsResource
+    identity: identity.IdentityResource
+    organizations: organizations.OrganizationsResource
+    projects: projects.ProjectsResource
+    runners: runners.RunnersResource
+    secrets: secrets.SecretsResource
+    usage: usage.UsageResource
+    users: users.UsersResource
+    with_raw_response: GitpodWithRawResponse
+    with_streaming_response: GitpodWithStreamedResponse
+
+    # client options
+    bearer_token: str
+
+    def __init__(
+        self,
+        *,
+        bearer_token: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        # Configure a custom httpx client.
+        # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
+        # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
+        http_client: httpx.Client | None = None,
+        # Enable or disable schema validation for data returned by the API.
+        # When enabled an error APIResponseValidationError is raised
+        # if the API responds with invalid data for the expected schema.
+        #
+        # This parameter may be removed or changed in the future.
+        # If you rely on this feature, please open a GitHub issue
+        # outlining your use-case to help us decide if it should be
+        # part of our public interface in the future.
+        _strict_response_validation: bool = False,
+    ) -> None:
+        """Construct a new synchronous Gitpod client instance.
+
+        This automatically infers the `bearer_token` argument from the `GITPOD_API_KEY` environment variable if it is not provided.
+        """
+        if bearer_token is None:
+            bearer_token = os.environ.get("GITPOD_API_KEY")
+        if bearer_token is None:
+            raise GitpodError(
+                "The bearer_token client option must be set either by passing bearer_token to the client or by setting the GITPOD_API_KEY environment variable"
+            )
+        self.bearer_token = bearer_token
+
+        if base_url is None:
+            base_url = os.environ.get("GITPOD_BASE_URL")
+        if base_url is None:
+            base_url = f"https://app.gitpod.io/api"
+
+        super().__init__(
+            version=__version__,
+            base_url=base_url,
+            max_retries=max_retries,
+            timeout=timeout,
+            http_client=http_client,
+            custom_headers=default_headers,
+            custom_query=default_query,
+            _strict_response_validation=_strict_response_validation,
+        )
+
+        self.accounts = accounts.AccountsResource(self)
+        self.editors = editors.EditorsResource(self)
+        self.environments = environments.EnvironmentsResource(self)
+        self.events = events.EventsResource(self)
+        self.gateways = gateways.GatewaysResource(self)
+        self.groups = groups.GroupsResource(self)
+        self.identity = identity.IdentityResource(self)
+        self.organizations = organizations.OrganizationsResource(self)
+        self.projects = projects.ProjectsResource(self)
+        self.runners = runners.RunnersResource(self)
+        self.secrets = secrets.SecretsResource(self)
+        self.usage = usage.UsageResource(self)
+        self.users = users.UsersResource(self)
+        self.with_raw_response = GitpodWithRawResponse(self)
+        self.with_streaming_response = GitpodWithStreamedResponse(self)
+
+    @property
+    @override
+    def qs(self) -> Querystring:
+        return Querystring(array_format="comma")
+
+    @property
+    @override
+    def auth_headers(self) -> dict[str, str]:
+        bearer_token = self.bearer_token
+        return {"Authorization": f"Bearer {bearer_token}"}
+
+    @property
+    @override
+    def default_headers(self) -> dict[str, str | Omit]:
+        return {
+            **super().default_headers,
+            "X-Stainless-Async": "false",
+            **self._custom_headers,
+        }
+
+    def copy(
+        self,
+        *,
+        bearer_token: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        http_client: httpx.Client | None = None,
+        max_retries: int | NotGiven = NOT_GIVEN,
+        default_headers: Mapping[str, str] | None = None,
+        set_default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        set_default_query: Mapping[str, object] | None = None,
+        _extra_kwargs: Mapping[str, Any] = {},
+    ) -> Self:
+        """
+        Create a new client instance re-using the same options given to the current client with optional overriding.
+        """
+        if default_headers is not None and set_default_headers is not None:
+            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
+
+        if default_query is not None and set_default_query is not None:
+            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
+
+        headers = self._custom_headers
+        if default_headers is not None:
+            headers = {**headers, **default_headers}
+        elif set_default_headers is not None:
+            headers = set_default_headers
+
+        params = self._custom_query
+        if default_query is not None:
+            params = {**params, **default_query}
+        elif set_default_query is not None:
+            params = set_default_query
+
+        http_client = http_client or self._client
+        return self.__class__(
+            bearer_token=bearer_token or self.bearer_token,
+            base_url=base_url or self.base_url,
+            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
+            http_client=http_client,
+            max_retries=max_retries if is_given(max_retries) else self.max_retries,
+            default_headers=headers,
+            default_query=params,
+            **_extra_kwargs,
+        )
+
+    # Alias for `copy` for nicer inline usage, e.g.
+    # client.with_options(timeout=10).foo.create(...)
+    with_options = copy
+
+    @override
+    def _make_status_error(
+        self,
+        err_msg: str,
+        *,
+        body: object,
+        response: httpx.Response,
+    ) -> APIStatusError:
+        if response.status_code == 400:
+            return _exceptions.BadRequestError(err_msg, response=response, body=body)
+
+        if response.status_code == 401:
+            return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+
+        if response.status_code == 403:
+            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+
+        if response.status_code == 404:
+            return _exceptions.NotFoundError(err_msg, response=response, body=body)
+
+        if response.status_code == 409:
+            return _exceptions.ConflictError(err_msg, response=response, body=body)
+
+        if response.status_code == 422:
+            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
+
+        if response.status_code == 429:
+            return _exceptions.RateLimitError(err_msg, response=response, body=body)
+
+        if response.status_code >= 500:
+            return _exceptions.InternalServerError(err_msg, response=response, body=body)
+        return APIStatusError(err_msg, response=response, body=body)
+
+
+class AsyncGitpod(AsyncAPIClient):
+    accounts: accounts.AsyncAccountsResource
+    editors: editors.AsyncEditorsResource
+    environments: environments.AsyncEnvironmentsResource
+    events: events.AsyncEventsResource
+    gateways: gateways.AsyncGatewaysResource
+    groups: groups.AsyncGroupsResource
+    identity: identity.AsyncIdentityResource
+    organizations: organizations.AsyncOrganizationsResource
+    projects: projects.AsyncProjectsResource
+    runners: runners.AsyncRunnersResource
+    secrets: secrets.AsyncSecretsResource
+    usage: usage.AsyncUsageResource
+    users: users.AsyncUsersResource
+    with_raw_response: AsyncGitpodWithRawResponse
+    with_streaming_response: AsyncGitpodWithStreamedResponse
+
+    # client options
+    bearer_token: str
+
+    def __init__(
+        self,
+        *,
+        bearer_token: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        # Configure a custom httpx client.
+        # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
+        # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
+        http_client: httpx.AsyncClient | None = None,
+        # Enable or disable schema validation for data returned by the API.
+        # When enabled an error APIResponseValidationError is raised
+        # if the API responds with invalid data for the expected schema.
+        #
+        # This parameter may be removed or changed in the future.
+        # If you rely on this feature, please open a GitHub issue
+        # outlining your use-case to help us decide if it should be
+        # part of our public interface in the future.
+        _strict_response_validation: bool = False,
+    ) -> None:
+        """Construct a new async AsyncGitpod client instance.
+
+        This automatically infers the `bearer_token` argument from the `GITPOD_API_KEY` environment variable if it is not provided.
+        """
+        if bearer_token is None:
+            bearer_token = os.environ.get("GITPOD_API_KEY")
+        if bearer_token is None:
+            raise GitpodError(
+                "The bearer_token client option must be set either by passing bearer_token to the client or by setting the GITPOD_API_KEY environment variable"
+            )
+        self.bearer_token = bearer_token
+
+        if base_url is None:
+            base_url = os.environ.get("GITPOD_BASE_URL")
+        if base_url is None:
+            base_url = f"https://app.gitpod.io/api"
+
+        super().__init__(
+            version=__version__,
+            base_url=base_url,
+            max_retries=max_retries,
+            timeout=timeout,
+            http_client=http_client,
+            custom_headers=default_headers,
+            custom_query=default_query,
+            _strict_response_validation=_strict_response_validation,
+        )
+
+        self.accounts = accounts.AsyncAccountsResource(self)
+        self.editors = editors.AsyncEditorsResource(self)
+        self.environments = environments.AsyncEnvironmentsResource(self)
+        self.events = events.AsyncEventsResource(self)
+        self.gateways = gateways.AsyncGatewaysResource(self)
+        self.groups = groups.AsyncGroupsResource(self)
+        self.identity = identity.AsyncIdentityResource(self)
+        self.organizations = organizations.AsyncOrganizationsResource(self)
+        self.projects = projects.AsyncProjectsResource(self)
+        self.runners = runners.AsyncRunnersResource(self)
+        self.secrets = secrets.AsyncSecretsResource(self)
+        self.usage = usage.AsyncUsageResource(self)
+        self.users = users.AsyncUsersResource(self)
+        self.with_raw_response = AsyncGitpodWithRawResponse(self)
+        self.with_streaming_response = AsyncGitpodWithStreamedResponse(self)
+
+    @property
+    @override
+    def qs(self) -> Querystring:
+        return Querystring(array_format="comma")
+
+    @property
+    @override
+    def auth_headers(self) -> dict[str, str]:
+        bearer_token = self.bearer_token
+        return {"Authorization": f"Bearer {bearer_token}"}
+
+    @property
+    @override
+    def default_headers(self) -> dict[str, str | Omit]:
+        return {
+            **super().default_headers,
+            "X-Stainless-Async": f"async:{get_async_library()}",
+            **self._custom_headers,
+        }
+
+    def copy(
+        self,
+        *,
+        bearer_token: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        http_client: httpx.AsyncClient | None = None,
+        max_retries: int | NotGiven = NOT_GIVEN,
+        default_headers: Mapping[str, str] | None = None,
+        set_default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        set_default_query: Mapping[str, object] | None = None,
+        _extra_kwargs: Mapping[str, Any] = {},
+    ) -> Self:
+        """
+        Create a new client instance re-using the same options given to the current client with optional overriding.
+        """
+        if default_headers is not None and set_default_headers is not None:
+            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
+
+        if default_query is not None and set_default_query is not None:
+            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
+
+        headers = self._custom_headers
+        if default_headers is not None:
+            headers = {**headers, **default_headers}
+        elif set_default_headers is not None:
+            headers = set_default_headers
+
+        params = self._custom_query
+        if default_query is not None:
+            params = {**params, **default_query}
+        elif set_default_query is not None:
+            params = set_default_query
+
+        http_client = http_client or self._client
+        return self.__class__(
+            bearer_token=bearer_token or self.bearer_token,
+            base_url=base_url or self.base_url,
+            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
+            http_client=http_client,
+            max_retries=max_retries if is_given(max_retries) else self.max_retries,
+            default_headers=headers,
+            default_query=params,
+            **_extra_kwargs,
+        )
+
+    # Alias for `copy` for nicer inline usage, e.g.
+    # client.with_options(timeout=10).foo.create(...)
+    with_options = copy
+
+    @override
+    def _make_status_error(
+        self,
+        err_msg: str,
+        *,
+        body: object,
+        response: httpx.Response,
+    ) -> APIStatusError:
+        if response.status_code == 400:
+            return _exceptions.BadRequestError(err_msg, response=response, body=body)
+
+        if response.status_code == 401:
+            return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+
+        if response.status_code == 403:
+            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+
+        if response.status_code == 404:
+            return _exceptions.NotFoundError(err_msg, response=response, body=body)
+
+        if response.status_code == 409:
+            return _exceptions.ConflictError(err_msg, response=response, body=body)
+
+        if response.status_code == 422:
+            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
+
+        if response.status_code == 429:
+            return _exceptions.RateLimitError(err_msg, response=response, body=body)
+
+        if response.status_code >= 500:
+            return _exceptions.InternalServerError(err_msg, response=response, body=body)
+        return APIStatusError(err_msg, response=response, body=body)
+
+
+class GitpodWithRawResponse:
+    def __init__(self, client: Gitpod) -> None:
+        self.accounts = accounts.AccountsResourceWithRawResponse(client.accounts)
+        self.editors = editors.EditorsResourceWithRawResponse(client.editors)
+        self.environments = environments.EnvironmentsResourceWithRawResponse(client.environments)
+        self.events = events.EventsResourceWithRawResponse(client.events)
+        self.gateways = gateways.GatewaysResourceWithRawResponse(client.gateways)
+        self.groups = groups.GroupsResourceWithRawResponse(client.groups)
+        self.identity = identity.IdentityResourceWithRawResponse(client.identity)
+        self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations)
+        self.projects = projects.ProjectsResourceWithRawResponse(client.projects)
+        self.runners = runners.RunnersResourceWithRawResponse(client.runners)
+        self.secrets = secrets.SecretsResourceWithRawResponse(client.secrets)
+        self.usage = usage.UsageResourceWithRawResponse(client.usage)
+        self.users = users.UsersResourceWithRawResponse(client.users)
+
+
+class AsyncGitpodWithRawResponse:
+    def __init__(self, client: AsyncGitpod) -> None:
+        self.accounts = accounts.AsyncAccountsResourceWithRawResponse(client.accounts)
+        self.editors = editors.AsyncEditorsResourceWithRawResponse(client.editors)
+        self.environments = environments.AsyncEnvironmentsResourceWithRawResponse(client.environments)
+        self.events = events.AsyncEventsResourceWithRawResponse(client.events)
+        self.gateways = gateways.AsyncGatewaysResourceWithRawResponse(client.gateways)
+        self.groups = groups.AsyncGroupsResourceWithRawResponse(client.groups)
+        self.identity = identity.AsyncIdentityResourceWithRawResponse(client.identity)
+        self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations)
+        self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects)
+        self.runners = runners.AsyncRunnersResourceWithRawResponse(client.runners)
+        self.secrets = secrets.AsyncSecretsResourceWithRawResponse(client.secrets)
+        self.usage = usage.AsyncUsageResourceWithRawResponse(client.usage)
+        self.users = users.AsyncUsersResourceWithRawResponse(client.users)
+
+
+class GitpodWithStreamedResponse:
+    def __init__(self, client: Gitpod) -> None:
+        self.accounts = accounts.AccountsResourceWithStreamingResponse(client.accounts)
+        self.editors = editors.EditorsResourceWithStreamingResponse(client.editors)
+        self.environments = environments.EnvironmentsResourceWithStreamingResponse(client.environments)
+        self.events = events.EventsResourceWithStreamingResponse(client.events)
+        self.gateways = gateways.GatewaysResourceWithStreamingResponse(client.gateways)
+        self.groups = groups.GroupsResourceWithStreamingResponse(client.groups)
+        self.identity = identity.IdentityResourceWithStreamingResponse(client.identity)
+        self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations)
+        self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects)
+        self.runners = runners.RunnersResourceWithStreamingResponse(client.runners)
+        self.secrets = secrets.SecretsResourceWithStreamingResponse(client.secrets)
+        self.usage = usage.UsageResourceWithStreamingResponse(client.usage)
+        self.users = users.UsersResourceWithStreamingResponse(client.users)
+
+
+class AsyncGitpodWithStreamedResponse:
+    def __init__(self, client: AsyncGitpod) -> None:
+        self.accounts = accounts.AsyncAccountsResourceWithStreamingResponse(client.accounts)
+        self.editors = editors.AsyncEditorsResourceWithStreamingResponse(client.editors)
+        self.environments = environments.AsyncEnvironmentsResourceWithStreamingResponse(client.environments)
+        self.events = events.AsyncEventsResourceWithStreamingResponse(client.events)
+        self.gateways = gateways.AsyncGatewaysResourceWithStreamingResponse(client.gateways)
+        self.groups = groups.AsyncGroupsResourceWithStreamingResponse(client.groups)
+        self.identity = identity.AsyncIdentityResourceWithStreamingResponse(client.identity)
+        self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations)
+        self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects)
+        self.runners = runners.AsyncRunnersResourceWithStreamingResponse(client.runners)
+        self.secrets = secrets.AsyncSecretsResourceWithStreamingResponse(client.secrets)
+        self.usage = usage.AsyncUsageResourceWithStreamingResponse(client.usage)
+        self.users = users.AsyncUsersResourceWithStreamingResponse(client.users)
+
+
+Client = Gitpod
+
+AsyncClient = AsyncGitpod
diff --git a/src/gitpod/_compat.py b/src/gitpod/_compat.py
new file mode 100644
index 0000000..92d9ee6
--- /dev/null
+++ b/src/gitpod/_compat.py
@@ -0,0 +1,219 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
+from datetime import date, datetime
+from typing_extensions import Self, Literal
+
+import pydantic
+from pydantic.fields import FieldInfo
+
+from ._types import IncEx, StrBytesIntFloat
+
+_T = TypeVar("_T")
+_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)
+
+# --------------- Pydantic v2 compatibility ---------------
+
+# Pyright incorrectly reports some of our functions as overriding a method when they don't
+# pyright: reportIncompatibleMethodOverride=false
+
+PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
+
+# v1 re-exports
+if TYPE_CHECKING:
+
+    def parse_date(value: date | StrBytesIntFloat) -> date:  # noqa: ARG001
+        ...
+
+    def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:  # noqa: ARG001
+        ...
+
+    def get_args(t: type[Any]) -> tuple[Any, ...]:  # noqa: ARG001
+        ...
+
+    def is_union(tp: type[Any] | None) -> bool:  # noqa: ARG001
+        ...
+
+    def get_origin(t: type[Any]) -> type[Any] | None:  # noqa: ARG001
+        ...
+
+    def is_literal_type(type_: type[Any]) -> bool:  # noqa: ARG001
+        ...
+
+    def is_typeddict(type_: type[Any]) -> bool:  # noqa: ARG001
+        ...
+
+else:
+    if PYDANTIC_V2:
+        from pydantic.v1.typing import (
+            get_args as get_args,
+            is_union as is_union,
+            get_origin as get_origin,
+            is_typeddict as is_typeddict,
+            is_literal_type as is_literal_type,
+        )
+        from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
+    else:
+        from pydantic.typing import (
+            get_args as get_args,
+            is_union as is_union,
+            get_origin as get_origin,
+            is_typeddict as is_typeddict,
+            is_literal_type as is_literal_type,
+        )
+        from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
+
+
+# refactored config
+if TYPE_CHECKING:
+    from pydantic import ConfigDict as ConfigDict
+else:
+    if PYDANTIC_V2:
+        from pydantic import ConfigDict
+    else:
+        # TODO: provide an error message here?
+        ConfigDict = None
+
+
+# renamed methods / properties
+def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
+    if PYDANTIC_V2:
+        return model.model_validate(value)
+    else:
+        return cast(_ModelT, model.parse_obj(value))  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+
+
+def field_is_required(field: FieldInfo) -> bool:
+    if PYDANTIC_V2:
+        return field.is_required()
+    return field.required  # type: ignore
+
+
+def field_get_default(field: FieldInfo) -> Any:
+    value = field.get_default()
+    if PYDANTIC_V2:
+        from pydantic_core import PydanticUndefined
+
+        if value == PydanticUndefined:
+            return None
+        return value
+    return value
+
+
+def field_outer_type(field: FieldInfo) -> Any:
+    if PYDANTIC_V2:
+        return field.annotation
+    return field.outer_type_  # type: ignore
+
+
+def get_model_config(model: type[pydantic.BaseModel]) -> Any:
+    if PYDANTIC_V2:
+        return model.model_config
+    return model.__config__  # type: ignore
+
+
+def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
+    if PYDANTIC_V2:
+        return model.model_fields
+    return model.__fields__  # type: ignore
+
+
+def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
+    if PYDANTIC_V2:
+        return model.model_copy(deep=deep)
+    return model.copy(deep=deep)  # type: ignore
+
+
+def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
+    if PYDANTIC_V2:
+        return model.model_dump_json(indent=indent)
+    return model.json(indent=indent)  # type: ignore
+
+
+def model_dump(
+    model: pydantic.BaseModel,
+    *,
+    exclude: IncEx | None = None,
+    exclude_unset: bool = False,
+    exclude_defaults: bool = False,
+    warnings: bool = True,
+    mode: Literal["json", "python"] = "python",
+) -> dict[str, Any]:
+    if PYDANTIC_V2 or hasattr(model, "model_dump"):
+        return model.model_dump(
+            mode=mode,
+            exclude=exclude,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            # warnings are not supported in Pydantic v1
+            warnings=warnings if PYDANTIC_V2 else True,
+        )
+    return cast(
+        "dict[str, Any]",
+        model.dict(  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+            exclude=exclude,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+        ),
+    )
+
+
+def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
+    if PYDANTIC_V2:
+        return model.model_validate(data)
+    return model.parse_obj(data)  # pyright: ignore[reportDeprecated]
+
+
+# generic models
+if TYPE_CHECKING:
+
+    class GenericModel(pydantic.BaseModel): ...
+
+else:
+    if PYDANTIC_V2:
+        # there no longer needs to be a distinction in v2 but
+        # we still have to create our own subclass to avoid
+        # inconsistent MRO ordering errors
+        class GenericModel(pydantic.BaseModel): ...
+
+    else:
+        import pydantic.generics
+
+        class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
+
+
+# cached properties
+if TYPE_CHECKING:
+    cached_property = property
+
+    # we define a separate type (copied from typeshed)
+    # that represents that `cached_property` is `set`able
+    # at runtime, which differs from `@property`.
+    #
+    # this is a separate type as editors likely special case
+    # `@property` and we don't want to cause issues just to have
+    # more helpful internal types.
+
+    class typed_cached_property(Generic[_T]):
+        func: Callable[[Any], _T]
+        attrname: str | None
+
+        def __init__(self, func: Callable[[Any], _T]) -> None: ...
+
+        @overload
+        def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...
+
+        @overload
+        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ...
+
+        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self:
+            raise NotImplementedError()
+
+        def __set_name__(self, owner: type[Any], name: str) -> None: ...
+
+        # __set__ is not defined at runtime, but @cached_property is designed to be settable
+        def __set__(self, instance: object, value: _T) -> None: ...
+else:
+    from functools import cached_property as cached_property
+
+    typed_cached_property = cached_property
diff --git a/src/gitpod/_constants.py b/src/gitpod/_constants.py
new file mode 100644
index 0000000..6ddf2c7
--- /dev/null
+++ b/src/gitpod/_constants.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import httpx
+
+RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
+OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
+
+# default timeout is 1 minute
+DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
+DEFAULT_MAX_RETRIES = 2
+DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
+
+INITIAL_RETRY_DELAY = 0.5
+MAX_RETRY_DELAY = 8.0
diff --git a/src/gitpod/_decoders/jsonl.py b/src/gitpod/_decoders/jsonl.py
new file mode 100644
index 0000000..ac5ac74
--- /dev/null
+++ b/src/gitpod/_decoders/jsonl.py
@@ -0,0 +1,123 @@
+from __future__ import annotations
+
+import json
+from typing_extensions import Generic, TypeVar, Iterator, AsyncIterator
+
+import httpx
+
+from .._models import construct_type_unchecked
+
+_T = TypeVar("_T")
+
+
+class JSONLDecoder(Generic[_T]):
+    """A decoder for [JSON Lines](https://jsonlines.org) format.
+
+    This class provides an iterator over a byte-iterator that parses each JSON Line
+    into a given type.
+    """
+
+    http_response: httpx.Response
+    """The HTTP response this decoder was constructed from"""
+
+    def __init__(
+        self,
+        *,
+        raw_iterator: Iterator[bytes],
+        line_type: type[_T],
+        http_response: httpx.Response,
+    ) -> None:
+        super().__init__()
+        self.http_response = http_response
+        self._raw_iterator = raw_iterator
+        self._line_type = line_type
+        self._iterator = self.__decode__()
+
+    def close(self) -> None:
+        """Close the response body stream.
+
+        This is called automatically if you consume the entire stream.
+        """
+        self.http_response.close()
+
+    def __decode__(self) -> Iterator[_T]:
+        buf = b""
+        for chunk in self._raw_iterator:
+            for line in chunk.splitlines(keepends=True):
+                buf += line
+                if buf.endswith((b"\r", b"\n", b"\r\n")):
+                    yield construct_type_unchecked(
+                        value=json.loads(buf),
+                        type_=self._line_type,
+                    )
+                    buf = b""
+
+        # flush
+        if buf:
+            yield construct_type_unchecked(
+                value=json.loads(buf),
+                type_=self._line_type,
+            )
+
+    def __next__(self) -> _T:
+        return self._iterator.__next__()
+
+    def __iter__(self) -> Iterator[_T]:
+        for item in self._iterator:
+            yield item
+
+
+class AsyncJSONLDecoder(Generic[_T]):
+    """A decoder for [JSON Lines](https://jsonlines.org) format.
+
+    This class provides an async iterator over a byte-iterator that parses each JSON Line
+    into a given type.
+    """
+
+    http_response: httpx.Response
+
+    def __init__(
+        self,
+        *,
+        raw_iterator: AsyncIterator[bytes],
+        line_type: type[_T],
+        http_response: httpx.Response,
+    ) -> None:
+        super().__init__()
+        self.http_response = http_response
+        self._raw_iterator = raw_iterator
+        self._line_type = line_type
+        self._iterator = self.__decode__()
+
+    async def close(self) -> None:
+        """Close the response body stream.
+
+        This is called automatically if you consume the entire stream.
+        """
+        await self.http_response.aclose()
+
+    async def __decode__(self) -> AsyncIterator[_T]:
+        buf = b""
+        async for chunk in self._raw_iterator:
+            for line in chunk.splitlines(keepends=True):
+                buf += line
+                if buf.endswith((b"\r", b"\n", b"\r\n")):
+                    yield construct_type_unchecked(
+                        value=json.loads(buf),
+                        type_=self._line_type,
+                    )
+                    buf = b""
+
+        # flush
+        if buf:
+            yield construct_type_unchecked(
+                value=json.loads(buf),
+                type_=self._line_type,
+            )
+
+    async def __anext__(self) -> _T:
+        return await self._iterator.__anext__()
+
+    async def __aiter__(self) -> AsyncIterator[_T]:
+        async for item in self._iterator:
+            yield item
diff --git a/src/gitpod/_exceptions.py b/src/gitpod/_exceptions.py
new file mode 100644
index 0000000..bb138a9
--- /dev/null
+++ b/src/gitpod/_exceptions.py
@@ -0,0 +1,124 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Any, Optional, cast
+from typing_extensions import Literal
+
+import httpx
+
+from ._utils import is_dict
+from ._models import construct_type
+from .types.shared.error_code import ErrorCode
+
+__all__ = [
+    "BadRequestError",
+    "AuthenticationError",
+    "PermissionDeniedError",
+    "NotFoundError",
+    "ConflictError",
+    "UnprocessableEntityError",
+    "RateLimitError",
+    "InternalServerError",
+]
+
+
+class GitpodError(Exception):
+    pass
+
+
+class APIError(GitpodError):
+    message: str
+    request: httpx.Request
+
+    body: object | None
+    """The API response body.
+
+    If the API responded with a valid JSON structure then this property will be the
+    decoded result.
+
+    If it isn't a valid JSON structure then this will be the raw response.
+
+    If there was no response associated with this error then it will be `None`.
+    """
+
+    code: Optional[ErrorCode] = None
+    """
+    The status code, which should be an enum value of
+    [google.rpc.Code][google.rpc.Code].
+    """
+
+    def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:
+        super().__init__(message)
+        self.request = request
+        self.message = message
+        self.body = body
+
+        if is_dict(body):
+            self.code = cast(Any, construct_type(type_=Optional[ErrorCode], value=body.get("code")))
+        else:
+            self.code = None
+
+
+class APIResponseValidationError(APIError):
+    response: httpx.Response
+    status_code: int
+
+    def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
+        super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body)
+        self.response = response
+        self.status_code = response.status_code
+
+
+class APIStatusError(APIError):
+    """Raised when an API response has a status code of 4xx or 5xx."""
+
+    response: httpx.Response
+    status_code: int
+
+    def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None:
+        super().__init__(message, response.request, body=body)
+        self.response = response
+        self.status_code = response.status_code
+
+
+class APIConnectionError(APIError):
+    def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None:
+        super().__init__(message, request, body=None)
+
+
+class APITimeoutError(APIConnectionError):
+    def __init__(self, request: httpx.Request) -> None:
+        super().__init__(message="Request timed out.", request=request)
+
+
+class BadRequestError(APIStatusError):
+    status_code: Literal[400] = 400  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class AuthenticationError(APIStatusError):
+    status_code: Literal[401] = 401  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class PermissionDeniedError(APIStatusError):
+    status_code: Literal[403] = 403  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class NotFoundError(APIStatusError):
+    status_code: Literal[404] = 404  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class ConflictError(APIStatusError):
+    status_code: Literal[409] = 409  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class UnprocessableEntityError(APIStatusError):
+    status_code: Literal[422] = 422  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class RateLimitError(APIStatusError):
+    status_code: Literal[429] = 429  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class InternalServerError(APIStatusError):
+    pass
diff --git a/src/gitpod/_files.py b/src/gitpod/_files.py
new file mode 100644
index 0000000..715cc20
--- /dev/null
+++ b/src/gitpod/_files.py
@@ -0,0 +1,123 @@
+from __future__ import annotations
+
+import io
+import os
+import pathlib
+from typing import overload
+from typing_extensions import TypeGuard
+
+import anyio
+
+from ._types import (
+    FileTypes,
+    FileContent,
+    RequestFiles,
+    HttpxFileTypes,
+    Base64FileInput,
+    HttpxFileContent,
+    HttpxRequestFiles,
+)
+from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
+
+
+def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
+    return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
+
+
+def is_file_content(obj: object) -> TypeGuard[FileContent]:
+    return (
+        isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
+    )
+
+
+def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
+    if not is_file_content(obj):
+        prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
+        raise RuntimeError(
+            f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead."
+        ) from None
+
+
+@overload
+def to_httpx_files(files: None) -> None: ...
+
+
+@overload
+def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
+
+
+def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
+    if files is None:
+        return None
+
+    if is_mapping_t(files):
+        files = {key: _transform_file(file) for key, file in files.items()}
+    elif is_sequence_t(files):
+        files = [(key, _transform_file(file)) for key, file in files]
+    else:
+        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
+
+    return files
+
+
+def _transform_file(file: FileTypes) -> HttpxFileTypes:
+    if is_file_content(file):
+        if isinstance(file, os.PathLike):
+            path = pathlib.Path(file)
+            return (path.name, path.read_bytes())
+
+        return file
+
+    if is_tuple_t(file):
+        return (file[0], _read_file_content(file[1]), *file[2:])
+
+    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
+
+
+def _read_file_content(file: FileContent) -> HttpxFileContent:
+    if isinstance(file, os.PathLike):
+        return pathlib.Path(file).read_bytes()
+    return file
+
+
+@overload
+async def async_to_httpx_files(files: None) -> None: ...
+
+
+@overload
+async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
+
+
+async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
+    if files is None:
+        return None
+
+    if is_mapping_t(files):
+        files = {key: await _async_transform_file(file) for key, file in files.items()}
+    elif is_sequence_t(files):
+        files = [(key, await _async_transform_file(file)) for key, file in files]
+    else:
+        raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
+
+    return files
+
+
+async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
+    if is_file_content(file):
+        if isinstance(file, os.PathLike):
+            path = anyio.Path(file)
+            return (path.name, await path.read_bytes())
+
+        return file
+
+    if is_tuple_t(file):
+        return (file[0], await _async_read_file_content(file[1]), *file[2:])
+
+    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
+
+
+async def _async_read_file_content(file: FileContent) -> HttpxFileContent:
+    if isinstance(file, os.PathLike):
+        return await anyio.Path(file).read_bytes()
+
+    return file
diff --git a/src/gitpod/_models.py b/src/gitpod/_models.py
new file mode 100644
index 0000000..528d568
--- /dev/null
+++ b/src/gitpod/_models.py
@@ -0,0 +1,808 @@
+from __future__ import annotations
+
+import os
+import inspect
+from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
+from datetime import date, datetime
+from typing_extensions import (
+    List,
+    Unpack,
+    Literal,
+    ClassVar,
+    Protocol,
+    Required,
+    ParamSpec,
+    TypedDict,
+    TypeGuard,
+    final,
+    override,
+    runtime_checkable,
+)
+
+import pydantic
+from pydantic.fields import FieldInfo
+
+from ._types import (
+    Body,
+    IncEx,
+    Query,
+    ModelT,
+    Headers,
+    Timeout,
+    NotGiven,
+    AnyMapping,
+    HttpxRequestFiles,
+)
+from ._utils import (
+    PropertyInfo,
+    is_list,
+    is_given,
+    json_safe,
+    lru_cache,
+    is_mapping,
+    parse_date,
+    coerce_boolean,
+    parse_datetime,
+    strip_not_given,
+    extract_type_arg,
+    is_annotated_type,
+    is_type_alias_type,
+    strip_annotated_type,
+)
+from ._compat import (
+    PYDANTIC_V2,
+    ConfigDict,
+    GenericModel as BaseGenericModel,
+    get_args,
+    is_union,
+    parse_obj,
+    get_origin,
+    is_literal_type,
+    get_model_config,
+    get_model_fields,
+    field_get_default,
+)
+from ._constants import RAW_RESPONSE_HEADER
+
+if TYPE_CHECKING:
+    from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
+
+__all__ = ["BaseModel", "GenericModel"]
+
+_T = TypeVar("_T")
+_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel")
+
+P = ParamSpec("P")
+
+
+@runtime_checkable
+class _ConfigProtocol(Protocol):
+    allow_population_by_field_name: bool
+
+
+class BaseModel(pydantic.BaseModel):
+    if PYDANTIC_V2:
+        model_config: ClassVar[ConfigDict] = ConfigDict(
+            extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true"))
+        )
+    else:
+
+        @property
+        @override
+        def model_fields_set(self) -> set[str]:
+            # a forwards-compat shim for pydantic v2
+            return self.__fields_set__  # type: ignore
+
+        class Config(pydantic.BaseConfig):  # pyright: ignore[reportDeprecated]
+            extra: Any = pydantic.Extra.allow  # type: ignore
+
+    def to_dict(
+        self,
+        *,
+        mode: Literal["json", "python"] = "python",
+        use_api_names: bool = True,
+        exclude_unset: bool = True,
+        exclude_defaults: bool = False,
+        exclude_none: bool = False,
+        warnings: bool = True,
+    ) -> dict[str, object]:
+        """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
+
+        By default, fields that were not set by the API will not be included,
+        and keys will match the API response, *not* the property names from the model.
+
+        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
+        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).
+
+        Args:
+            mode:
+                If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`.
+                If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)`
+
+            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
+            exclude_unset: Whether to exclude fields that have not been explicitly set.
+            exclude_defaults: Whether to exclude fields that are set to their default value from the output.
+            exclude_none: Whether to exclude fields that have a value of `None` from the output.
+            warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2.
+        """
+        return self.model_dump(
+            mode=mode,
+            by_alias=use_api_names,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
+            warnings=warnings,
+        )
+
+    def to_json(
+        self,
+        *,
+        indent: int | None = 2,
+        use_api_names: bool = True,
+        exclude_unset: bool = True,
+        exclude_defaults: bool = False,
+        exclude_none: bool = False,
+        warnings: bool = True,
+    ) -> str:
+        """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation).
+
+        By default, fields that were not set by the API will not be included,
+        and keys will match the API response, *not* the property names from the model.
+
+        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
+        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).
+
+        Args:
+            indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2`
+            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
+            exclude_unset: Whether to exclude fields that have not been explicitly set.
+            exclude_defaults: Whether to exclude fields that have the default value.
+            exclude_none: Whether to exclude fields that have a value of `None`.
+            warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2.
+        """
+        return self.model_dump_json(
+            indent=indent,
+            by_alias=use_api_names,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
+            warnings=warnings,
+        )
+
+    @override
+    def __str__(self) -> str:
+        # mypy complains about an invalid self arg
+        return f"{self.__repr_name__()}({self.__repr_str__(', ')})"  # type: ignore[misc]
+
+    # Override the 'construct' method in a way that supports recursive parsing without validation.
+    # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
+    @classmethod
+    @override
+    def construct(  # pyright: ignore[reportIncompatibleMethodOverride]
+        __cls: Type[ModelT],
+        _fields_set: set[str] | None = None,
+        **values: object,
+    ) -> ModelT:
+        m = __cls.__new__(__cls)
+        fields_values: dict[str, object] = {}
+
+        config = get_model_config(__cls)
+        populate_by_name = (
+            config.allow_population_by_field_name
+            if isinstance(config, _ConfigProtocol)
+            else config.get("populate_by_name")
+        )
+
+        if _fields_set is None:
+            _fields_set = set()
+
+        model_fields = get_model_fields(__cls)
+        for name, field in model_fields.items():
+            key = field.alias
+            if key is None or (key not in values and populate_by_name):
+                key = name
+
+            if key in values:
+                fields_values[name] = _construct_field(value=values[key], field=field, key=key)
+                _fields_set.add(name)
+            else:
+                fields_values[name] = field_get_default(field)
+
+        _extra = {}
+        for key, value in values.items():
+            if key not in model_fields:
+                if PYDANTIC_V2:
+                    _extra[key] = value
+                else:
+                    _fields_set.add(key)
+                    fields_values[key] = value
+
+        object.__setattr__(m, "__dict__", fields_values)
+
+        if PYDANTIC_V2:
+            # these properties are copied from Pydantic's `model_construct()` method
+            object.__setattr__(m, "__pydantic_private__", None)
+            object.__setattr__(m, "__pydantic_extra__", _extra)
+            object.__setattr__(m, "__pydantic_fields_set__", _fields_set)
+        else:
+            # init_private_attributes() does not exist in v2
+            m._init_private_attributes()  # type: ignore
+
+            # copied from Pydantic v1's `construct()` method
+            object.__setattr__(m, "__fields_set__", _fields_set)
+
+        return m
+
+    if not TYPE_CHECKING:
+        # type checkers incorrectly complain about this assignment
+        # because the type signatures are technically different
+        # although not in practice
+        model_construct = construct
+
+    if not PYDANTIC_V2:
+        # we define aliases for some of the new pydantic v2 methods so
+        # that we can just document these methods without having to specify
+        # a specific pydantic version as some users may not know which
+        # pydantic version they are currently using
+
+        @override
+        def model_dump(
+            self,
+            *,
+            mode: Literal["json", "python"] | str = "python",
+            include: IncEx | None = None,
+            exclude: IncEx | None = None,
+            by_alias: bool = False,
+            exclude_unset: bool = False,
+            exclude_defaults: bool = False,
+            exclude_none: bool = False,
+            round_trip: bool = False,
+            warnings: bool | Literal["none", "warn", "error"] = True,
+            context: dict[str, Any] | None = None,
+            serialize_as_any: bool = False,
+        ) -> dict[str, Any]:
+            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump
+
+            Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
+
+            Args:
+                mode: The mode in which `to_python` should run.
+                    If mode is 'json', the dictionary will only contain JSON serializable types.
+                    If mode is 'python', the dictionary may contain any Python objects.
+                include: A list of fields to include in the output.
+                exclude: A list of fields to exclude from the output.
+                by_alias: Whether to use the field's alias in the dictionary key if defined.
+                exclude_unset: Whether to exclude fields that are unset or None from the output.
+                exclude_defaults: Whether to exclude fields that are set to their default value from the output.
+                exclude_none: Whether to exclude fields that have a value of `None` from the output.
+                round_trip: Whether to enable serialization and deserialization round-trip support.
+                warnings: Whether to log warnings when invalid fields are encountered.
+
+            Returns:
+                A dictionary representation of the model.
+            """
+            if mode not in {"json", "python"}:
+                raise ValueError("mode must be either 'json' or 'python'")
+            if round_trip != False:
+                raise ValueError("round_trip is only supported in Pydantic v2")
+            if warnings != True:
+                raise ValueError("warnings is only supported in Pydantic v2")
+            if context is not None:
+                raise ValueError("context is only supported in Pydantic v2")
+            if serialize_as_any != False:
+                raise ValueError("serialize_as_any is only supported in Pydantic v2")
+            dumped = super().dict(  # pyright: ignore[reportDeprecated]
+                include=include,
+                exclude=exclude,
+                by_alias=by_alias,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
+
+            return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped
+
+        @override
+        def model_dump_json(
+            self,
+            *,
+            indent: int | None = None,
+            include: IncEx | None = None,
+            exclude: IncEx | None = None,
+            by_alias: bool = False,
+            exclude_unset: bool = False,
+            exclude_defaults: bool = False,
+            exclude_none: bool = False,
+            round_trip: bool = False,
+            warnings: bool | Literal["none", "warn", "error"] = True,
+            context: dict[str, Any] | None = None,
+            serialize_as_any: bool = False,
+        ) -> str:
+            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json
+
+            Generates a JSON representation of the model using Pydantic's `to_json` method.
+
+            Args:
+                indent: Indentation to use in the JSON output. If None is passed, the output will be compact.
+                include: Field(s) to include in the JSON output. Can take either a string or set of strings.
+                exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings.
+                by_alias: Whether to serialize using field aliases.
+                exclude_unset: Whether to exclude fields that have not been explicitly set.
+                exclude_defaults: Whether to exclude fields that have the default value.
+                exclude_none: Whether to exclude fields that have a value of `None`.
+                round_trip: Whether to use serialization/deserialization between JSON and class instance.
+                warnings: Whether to show any warnings that occurred during serialization.
+
+            Returns:
+                A JSON string representation of the model.
+            """
+            if round_trip != False:
+                raise ValueError("round_trip is only supported in Pydantic v2")
+            if warnings != True:
+                raise ValueError("warnings is only supported in Pydantic v2")
+            if context is not None:
+                raise ValueError("context is only supported in Pydantic v2")
+            if serialize_as_any != False:
+                raise ValueError("serialize_as_any is only supported in Pydantic v2")
+            return super().json(  # type: ignore[reportDeprecated]
+                indent=indent,
+                include=include,
+                exclude=exclude,
+                by_alias=by_alias,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
+
+
+def _construct_field(value: object, field: FieldInfo, key: str) -> object:
+    if value is None:
+        return field_get_default(field)
+
+    if PYDANTIC_V2:
+        type_ = field.annotation
+    else:
+        type_ = cast(type, field.outer_type_)  # type: ignore
+
+    if type_ is None:
+        raise RuntimeError(f"Unexpected field type is None for {key}")
+
+    return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
+
+
+def is_basemodel(type_: type) -> bool:
+    """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
+    if is_union(type_):
+        for variant in get_args(type_):
+            if is_basemodel(variant):
+                return True
+
+        return False
+
+    return is_basemodel_type(type_)
+
+
+def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]:
+    origin = get_origin(type_) or type_
+    if not inspect.isclass(origin):
+        return False
+    return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)
+
+
+def build(
+    base_model_cls: Callable[P, _BaseModelT],
+    *args: P.args,
+    **kwargs: P.kwargs,
+) -> _BaseModelT:
+    """Construct a BaseModel class without validation.
+
+    This is useful for cases where you need to instantiate a `BaseModel`
+    from an API response as this provides type-safe params which isn't supported
+    by helpers like `construct_type()`.
+
+    ```py
+    build(MyModel, my_field_a="foo", my_field_b=123)
+    ```
+    """
+    if args:
+        raise TypeError(
+            "Received positional arguments which are not supported; Keyword arguments must be used instead",
+        )
+
+    return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs))
+
+
+def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
+    """Loose coercion to the expected type with construction of nested values.
+
+    Note: the returned value from this function is not guaranteed to match the
+    given type.
+    """
+    return cast(_T, construct_type(value=value, type_=type_))
+
+
+def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
+    """Loose coercion to the expected type with construction of nested values.
+
+    If the given value does not match the expected type then it is returned as-is.
+    """
+
+    # store a reference to the original type we were given before we extract any inner
+    # types so that we can properly resolve forward references in `TypeAliasType` annotations
+    original_type = None
+
+    # we allow `object` as the input type because otherwise, passing things like
+    # `Literal['value']` will be reported as a type error by type checkers
+    type_ = cast("type[object]", type_)
+    if is_type_alias_type(type_):
+        original_type = type_  # type: ignore[unreachable]
+        type_ = type_.__value__  # type: ignore[unreachable]
+
+    # unwrap `Annotated[T, ...]` -> `T`
+    if metadata is not None:
+        meta: tuple[Any, ...] = tuple(metadata)
+    elif is_annotated_type(type_):
+        meta = get_args(type_)[1:]
+        type_ = extract_type_arg(type_, 0)
+    else:
+        meta = tuple()
+
+    # we need to use the origin class for any types that are subscripted generics
+    # e.g. Dict[str, object]
+    origin = get_origin(type_) or type_
+    args = get_args(type_)
+
+    if is_union(origin):
+        try:
+            return validate_type(type_=cast("type[object]", original_type or type_), value=value)
+        except Exception:
+            pass
+
+        # if the type is a discriminated union then we want to construct the right variant
+        # in the union, even if the data doesn't match exactly, otherwise we'd break code
+        # that relies on the constructed class types, e.g.
+        #
+        # class FooType:
+        #   kind: Literal['foo']
+        #   value: str
+        #
+        # class BarType:
+        #   kind: Literal['bar']
+        #   value: int
+        #
+        # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then
+        # we'd end up constructing `FooType` when it should be `BarType`.
+        discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
+        if discriminator and is_mapping(value):
+            variant_value = value.get(discriminator.field_alias_from or discriminator.field_name)
+            if variant_value and isinstance(variant_value, str):
+                variant_type = discriminator.mapping.get(variant_value)
+                if variant_type:
+                    return construct_type(type_=variant_type, value=value)
+
+        # if the data is not valid, use the first variant that doesn't fail while deserializing
+        for variant in args:
+            try:
+                return construct_type(value=value, type_=variant)
+            except Exception:
+                continue
+
+        raise RuntimeError(f"Could not convert data into a valid instance of {type_}")
+
+    if origin == dict:
+        if not is_mapping(value):
+            return value
+
+        _, items_type = get_args(type_)  # Dict[_, items_type]
+        return {key: construct_type(value=item, type_=items_type) for key, item in value.items()}
+
+    if (
+        not is_literal_type(type_)
+        and inspect.isclass(origin)
+        and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel))
+    ):
+        if is_list(value):
+            return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value]
+
+        if is_mapping(value):
+            if issubclass(type_, BaseModel):
+                return type_.construct(**value)  # type: ignore[arg-type]
+
+            return cast(Any, type_).construct(**value)
+
+    if origin == list:
+        if not is_list(value):
+            return value
+
+        inner_type = args[0]  # List[inner_type]
+        return [construct_type(value=entry, type_=inner_type) for entry in value]
+
+    if origin == float:
+        if isinstance(value, int):
+            coerced = float(value)
+            if coerced != value:
+                return value
+            return coerced
+
+        return value
+
+    if type_ == datetime:
+        try:
+            return parse_datetime(value)  # type: ignore
+        except Exception:
+            return value
+
+    if type_ == date:
+        try:
+            return parse_date(value)  # type: ignore
+        except Exception:
+            return value
+
+    return value
+
+
+@runtime_checkable
+class CachedDiscriminatorType(Protocol):
+    __discriminator__: DiscriminatorDetails
+
+
+class DiscriminatorDetails:
+    field_name: str
+    """The name of the discriminator field in the variant class, e.g.
+
+    ```py
+    class Foo(BaseModel):
+        type: Literal['foo']
+    ```
+
+    Will result in field_name='type'
+    """
+
+    field_alias_from: str | None
+    """The name of the discriminator field in the API response, e.g.
+
+    ```py
+    class Foo(BaseModel):
+        type: Literal['foo'] = Field(alias='type_from_api')
+    ```
+
+    Will result in field_alias_from='type_from_api'
+    """
+
+    mapping: dict[str, type]
+    """Mapping of discriminator value to variant type, e.g.
+
+    {'foo': FooVariant, 'bar': BarVariant}
+    """
+
+    def __init__(
+        self,
+        *,
+        mapping: dict[str, type],
+        discriminator_field: str,
+        discriminator_alias: str | None,
+    ) -> None:
+        self.mapping = mapping
+        self.field_name = discriminator_field
+        self.field_alias_from = discriminator_alias
+
+
+def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
+    if isinstance(union, CachedDiscriminatorType):
+        return union.__discriminator__
+
+    discriminator_field_name: str | None = None
+
+    for annotation in meta_annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None:
+            discriminator_field_name = annotation.discriminator
+            break
+
+    if not discriminator_field_name:
+        return None
+
+    mapping: dict[str, type] = {}
+    discriminator_alias: str | None = None
+
+    for variant in get_args(union):
+        variant = strip_annotated_type(variant)
+        if is_basemodel_type(variant):
+            if PYDANTIC_V2:
+                field = _extract_field_schema_pv2(variant, discriminator_field_name)
+                if not field:
+                    continue
+
+                # Note: if one variant defines an alias then they all should
+                discriminator_alias = field.get("serialization_alias")
+
+                field_schema = field["schema"]
+
+                if field_schema["type"] == "literal":
+                    for entry in cast("LiteralSchema", field_schema)["expected"]:
+                        if isinstance(entry, str):
+                            mapping[entry] = variant
+            else:
+                field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name)  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+                if not field_info:
+                    continue
+
+                # Note: if one variant defines an alias then they all should
+                discriminator_alias = field_info.alias
+
+                if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
+                    for entry in get_args(annotation):
+                        if isinstance(entry, str):
+                            mapping[entry] = variant
+
+    if not mapping:
+        return None
+
+    details = DiscriminatorDetails(
+        mapping=mapping,
+        discriminator_field=discriminator_field_name,
+        discriminator_alias=discriminator_alias,
+    )
+    cast(CachedDiscriminatorType, union).__discriminator__ = details
+    return details
+
+
+def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
+    schema = model.__pydantic_core_schema__
+    if schema["type"] == "definitions":
+        schema = schema["schema"]
+
+    if schema["type"] != "model":
+        return None
+
+    schema = cast("ModelSchema", schema)
+    fields_schema = schema["schema"]
+    if fields_schema["type"] != "model-fields":
+        return None
+
+    fields_schema = cast("ModelFieldsSchema", fields_schema)
+    field = fields_schema["fields"].get(field_name)
+    if not field:
+        return None
+
+    return cast("ModelField", field)  # pyright: ignore[reportUnnecessaryCast]
+
+
+def validate_type(*, type_: type[_T], value: object) -> _T:
+    """Strict validation that the given value matches the expected type"""
+    if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel):
+        return cast(_T, parse_obj(type_, value))
+
+    return cast(_T, _validate_non_model_type(type_=type_, value=value))
+
+
+def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None:
+    """Add a pydantic config for the given type.
+
+    Note: this is a no-op on Pydantic v1.
+    """
+    setattr(typ, "__pydantic_config__", config)  # noqa: B010
+
+
+# our use of subclassing here causes weirdness for type checkers,
+# so we just pretend that we don't subclass
+if TYPE_CHECKING:
+    GenericModel = BaseModel
+else:
+
+    class GenericModel(BaseGenericModel, BaseModel):
+        pass
+
+
+if PYDANTIC_V2:
+    from pydantic import TypeAdapter as _TypeAdapter
+
+    _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter))
+
+    if TYPE_CHECKING:
+        from pydantic import TypeAdapter
+    else:
+        TypeAdapter = _CachedTypeAdapter
+
+    def _validate_non_model_type(*, type_: type[_T], value: object) -> _T:
+        return TypeAdapter(type_).validate_python(value)
+
+elif not TYPE_CHECKING:  # TODO: condition is weird
+
+    class RootModel(GenericModel, Generic[_T]):
+        """Used as a placeholder to easily convert runtime types to a Pydantic format
+        to provide validation.
+
+        For example:
+        ```py
+        validated = RootModel[int](__root__="5").__root__
+        # validated: 5
+        ```
+        """
+
+        __root__: _T
+
+    def _validate_non_model_type(*, type_: type[_T], value: object) -> _T:
+        model = _create_pydantic_model(type_).validate(value)
+        return cast(_T, model.__root__)
+
+    def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]:
+        return RootModel[type_]  # type: ignore
+
+
+class FinalRequestOptionsInput(TypedDict, total=False):
+    method: Required[str]
+    url: Required[str]
+    params: Query
+    headers: Headers
+    max_retries: int
+    timeout: float | Timeout | None
+    files: HttpxRequestFiles | None
+    idempotency_key: str
+    json_data: Body
+    extra_json: AnyMapping
+    follow_redirects: bool
+
+
+@final
+class FinalRequestOptions(pydantic.BaseModel):
+    method: str
+    url: str
+    params: Query = {}
+    headers: Union[Headers, NotGiven] = NotGiven()
+    max_retries: Union[int, NotGiven] = NotGiven()
+    timeout: Union[float, Timeout, None, NotGiven] = NotGiven()
+    files: Union[HttpxRequestFiles, None] = None
+    idempotency_key: Union[str, None] = None
+    post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
+    follow_redirects: Union[bool, None] = None
+
+    # It should be noted that we cannot use `json` here as that would override
+    # a BaseModel method in an incompatible fashion.
+    json_data: Union[Body, None] = None
+    extra_json: Union[AnyMapping, None] = None
+
+    if PYDANTIC_V2:
+        model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
+    else:
+
+        class Config(pydantic.BaseConfig):  # pyright: ignore[reportDeprecated]
+            arbitrary_types_allowed: bool = True
+
+    def get_max_retries(self, max_retries: int) -> int:
+        if isinstance(self.max_retries, NotGiven):
+            return max_retries
+        return self.max_retries
+
+    def _strip_raw_response_header(self) -> None:
+        if not is_given(self.headers):
+            return
+
+        if self.headers.get(RAW_RESPONSE_HEADER):
+            self.headers = {**self.headers}
+            self.headers.pop(RAW_RESPONSE_HEADER)
+
+    # override the `construct` method so that we can run custom transformations.
+    # this is necessary as we don't want to do any actual runtime type checking
+    # (which means we can't use validators) but we do want to ensure that `NotGiven`
+    # values are not present
+    #
+    # type ignore required because we're adding explicit types to `**values`
+    @classmethod
+    def construct(  # type: ignore
+        cls,
+        _fields_set: set[str] | None = None,
+        **values: Unpack[FinalRequestOptionsInput],
+    ) -> FinalRequestOptions:
+        kwargs: dict[str, Any] = {
+            # we unconditionally call `strip_not_given` on any value
+            # as it will just ignore any non-mapping types
+            key: strip_not_given(value)
+            for key, value in values.items()
+        }
+        if PYDANTIC_V2:
+            return super().model_construct(_fields_set, **kwargs)
+        return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs))  # pyright: ignore[reportDeprecated]
+
+    if not TYPE_CHECKING:
+        # type checkers incorrectly complain about this assignment
+        model_construct = construct
diff --git a/src/gitpod/_qs.py b/src/gitpod/_qs.py
new file mode 100644
index 0000000..274320c
--- /dev/null
+++ b/src/gitpod/_qs.py
@@ -0,0 +1,150 @@
+from __future__ import annotations
+
+from typing import Any, List, Tuple, Union, Mapping, TypeVar
+from urllib.parse import parse_qs, urlencode
+from typing_extensions import Literal, get_args
+
+from ._types import NOT_GIVEN, NotGiven, NotGivenOr
+from ._utils import flatten
+
+_T = TypeVar("_T")
+
+
+ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
+NestedFormat = Literal["dots", "brackets"]
+
+PrimitiveData = Union[str, int, float, bool, None]
+# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
+# https://github.com/microsoft/pyright/issues/3555
+Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"]
+Params = Mapping[str, Data]
+
+
+class Querystring:
+    array_format: ArrayFormat
+    nested_format: NestedFormat
+
+    def __init__(
+        self,
+        *,
+        array_format: ArrayFormat = "repeat",
+        nested_format: NestedFormat = "brackets",
+    ) -> None:
+        self.array_format = array_format
+        self.nested_format = nested_format
+
+    def parse(self, query: str) -> Mapping[str, object]:
+        # Note: custom format syntax is not supported yet
+        return parse_qs(query)
+
+    def stringify(
+        self,
+        params: Params,
+        *,
+        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
+        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
+    ) -> str:
+        return urlencode(
+            self.stringify_items(
+                params,
+                array_format=array_format,
+                nested_format=nested_format,
+            )
+        )
+
+    def stringify_items(
+        self,
+        params: Params,
+        *,
+        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
+        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
+    ) -> list[tuple[str, str]]:
+        opts = Options(
+            qs=self,
+            array_format=array_format,
+            nested_format=nested_format,
+        )
+        return flatten([self._stringify_item(key, value, opts) for key, value in params.items()])
+
+    def _stringify_item(
+        self,
+        key: str,
+        value: Data,
+        opts: Options,
+    ) -> list[tuple[str, str]]:
+        if isinstance(value, Mapping):
+            items: list[tuple[str, str]] = []
+            nested_format = opts.nested_format
+            for subkey, subvalue in value.items():
+                items.extend(
+                    self._stringify_item(
+                        # TODO: error if unknown format
+                        f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]",
+                        subvalue,
+                        opts,
+                    )
+                )
+            return items
+
+        if isinstance(value, (list, tuple)):
+            array_format = opts.array_format
+            if array_format == "comma":
+                return [
+                    (
+                        key,
+                        ",".join(self._primitive_value_to_str(item) for item in value if item is not None),
+                    ),
+                ]
+            elif array_format == "repeat":
+                items = []
+                for item in value:
+                    items.extend(self._stringify_item(key, item, opts))
+                return items
+            elif array_format == "indices":
+                raise NotImplementedError("The array indices format is not supported yet")
+            elif array_format == "brackets":
+                items = []
+                key = key + "[]"
+                for item in value:
+                    items.extend(self._stringify_item(key, item, opts))
+                return items
+            else:
+                raise NotImplementedError(
+                    f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
+                )
+
+        serialised = self._primitive_value_to_str(value)
+        if not serialised:
+            return []
+        return [(key, serialised)]
+
+    def _primitive_value_to_str(self, value: PrimitiveData) -> str:
+        # copied from httpx
+        if value is True:
+            return "true"
+        elif value is False:
+            return "false"
+        elif value is None:
+            return ""
+        return str(value)
+
+
+_qs = Querystring()
+parse = _qs.parse
+stringify = _qs.stringify
+stringify_items = _qs.stringify_items
+
+
+class Options:
+    array_format: ArrayFormat
+    nested_format: NestedFormat
+
+    def __init__(
+        self,
+        qs: Querystring = _qs,
+        *,
+        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
+        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
+    ) -> None:
+        self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format
+        self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format
diff --git a/src/gitpod/_resource.py b/src/gitpod/_resource.py
new file mode 100644
index 0000000..7447644
--- /dev/null
+++ b/src/gitpod/_resource.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import time
+from typing import TYPE_CHECKING
+
+import anyio
+
+if TYPE_CHECKING:
+    from ._client import Gitpod, AsyncGitpod
+
+
+class SyncAPIResource:
+    _client: Gitpod
+
+    def __init__(self, client: Gitpod) -> None:
+        self._client = client
+        self._get = client.get
+        self._post = client.post
+        self._patch = client.patch
+        self._put = client.put
+        self._delete = client.delete
+        self._get_api_list = client.get_api_list
+
+    def _sleep(self, seconds: float) -> None:
+        time.sleep(seconds)
+
+
+class AsyncAPIResource:
+    _client: AsyncGitpod
+
+    def __init__(self, client: AsyncGitpod) -> None:
+        self._client = client
+        self._get = client.get
+        self._post = client.post
+        self._patch = client.patch
+        self._put = client.put
+        self._delete = client.delete
+        self._get_api_list = client.get_api_list
+
+    async def _sleep(self, seconds: float) -> None:
+        await anyio.sleep(seconds)
diff --git a/src/gitpod/_response.py b/src/gitpod/_response.py
new file mode 100644
index 0000000..6211d72
--- /dev/null
+++ b/src/gitpod/_response.py
@@ -0,0 +1,852 @@
+from __future__ import annotations
+
+import os
+import inspect
+import logging
+import datetime
+import functools
+from types import TracebackType
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Union,
+    Generic,
+    TypeVar,
+    Callable,
+    Iterator,
+    AsyncIterator,
+    cast,
+    overload,
+)
+from typing_extensions import Awaitable, ParamSpec, override, get_origin
+
+import anyio
+import httpx
+import pydantic
+
+from ._types import NoneType
+from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
+from ._models import BaseModel, is_basemodel
+from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
+from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
+from ._exceptions import GitpodError, APIResponseValidationError
+from ._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder
+
+if TYPE_CHECKING:
+    from ._models import FinalRequestOptions
+    from ._base_client import BaseClient
+
+
+P = ParamSpec("P")
+R = TypeVar("R")
+_T = TypeVar("_T")
+_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]")
+_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]")
+
+log: logging.Logger = logging.getLogger(__name__)
+
+
+class BaseAPIResponse(Generic[R]):
+    _cast_to: type[R]
+    _client: BaseClient[Any, Any]
+    _parsed_by_type: dict[type[Any], Any]
+    _is_sse_stream: bool
+    _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
+    _options: FinalRequestOptions
+
+    http_response: httpx.Response
+
+    retries_taken: int
+    """The number of retries made. If no retries happened this will be `0`"""
+
+    def __init__(
+        self,
+        *,
+        raw: httpx.Response,
+        cast_to: type[R],
+        client: BaseClient[Any, Any],
+        stream: bool,
+        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+        options: FinalRequestOptions,
+        retries_taken: int = 0,
+    ) -> None:
+        self._cast_to = cast_to
+        self._client = client
+        self._parsed_by_type = {}
+        self._is_sse_stream = stream
+        self._stream_cls = stream_cls
+        self._options = options
+        self.http_response = raw
+        self.retries_taken = retries_taken
+
+    @property
+    def headers(self) -> httpx.Headers:
+        return self.http_response.headers
+
+    @property
+    def http_request(self) -> httpx.Request:
+        """Returns the httpx Request instance associated with the current response."""
+        return self.http_response.request
+
+    @property
+    def status_code(self) -> int:
+        return self.http_response.status_code
+
+    @property
+    def url(self) -> httpx.URL:
+        """Returns the URL for which the request was made."""
+        return self.http_response.url
+
+    @property
+    def method(self) -> str:
+        return self.http_request.method
+
+    @property
+    def http_version(self) -> str:
+        return self.http_response.http_version
+
+    @property
+    def elapsed(self) -> datetime.timedelta:
+        """The time taken for the complete request/response cycle to complete."""
+        return self.http_response.elapsed
+
+    @property
+    def is_closed(self) -> bool:
+        """Whether or not the response body has been closed.
+
+        If this is False then there is response data that has not been read yet.
+        You must either fully consume the response body or call `.close()`
+        before discarding the response to prevent resource leaks.
+        """
+        return self.http_response.is_closed
+
+    @override
+    def __repr__(self) -> str:
+        return (
+            f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"
+        )
+
+    def _parse(self, *, to: type[_T] | None = None) -> R | _T:
+        cast_to = to if to is not None else self._cast_to
+
+        # unwrap `TypeAlias('Name', T)` -> `T`
+        if is_type_alias_type(cast_to):
+            cast_to = cast_to.__value__  # type: ignore[unreachable]
+
+        # unwrap `Annotated[T, ...]` -> `T`
+        if cast_to and is_annotated_type(cast_to):
+            cast_to = extract_type_arg(cast_to, 0)
+
+        origin = get_origin(cast_to) or cast_to
+
+        if inspect.isclass(origin):
+            if issubclass(cast(Any, origin), JSONLDecoder):
+                return cast(
+                    R,
+                    cast("type[JSONLDecoder[Any]]", cast_to)(
+                        raw_iterator=self.http_response.iter_bytes(chunk_size=64),
+                        line_type=extract_type_arg(cast_to, 0),
+                        http_response=self.http_response,
+                    ),
+                )
+
+            if issubclass(cast(Any, origin), AsyncJSONLDecoder):
+                return cast(
+                    R,
+                    cast("type[AsyncJSONLDecoder[Any]]", cast_to)(
+                        raw_iterator=self.http_response.aiter_bytes(chunk_size=64),
+                        line_type=extract_type_arg(cast_to, 0),
+                        http_response=self.http_response,
+                    ),
+                )
+
+        if self._is_sse_stream:
+            if to:
+                if not is_stream_class_type(to):
+                    raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")
+
+                return cast(
+                    _T,
+                    to(
+                        cast_to=extract_stream_chunk_type(
+                            to,
+                            failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
+                        ),
+                        response=self.http_response,
+                        client=cast(Any, self._client),
+                    ),
+                )
+
+            if self._stream_cls:
+                return cast(
+                    R,
+                    self._stream_cls(
+                        cast_to=extract_stream_chunk_type(self._stream_cls),
+                        response=self.http_response,
+                        client=cast(Any, self._client),
+                    ),
+                )
+
+            stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
+            if stream_cls is None:
+                raise MissingStreamClassError()
+
+            return cast(
+                R,
+                stream_cls(
+                    cast_to=cast_to,
+                    response=self.http_response,
+                    client=cast(Any, self._client),
+                ),
+            )
+
+        if cast_to is NoneType:
+            return cast(R, None)
+
+        response = self.http_response
+        if cast_to == str:
+            return cast(R, response.text)
+
+        if cast_to == bytes:
+            return cast(R, response.content)
+
+        if cast_to == int:
+            return cast(R, int(response.text))
+
+        if cast_to == float:
+            return cast(R, float(response.text))
+
+        if cast_to == bool:
+            return cast(R, response.text.lower() == "true")
+
+        if origin == APIResponse:
+            raise RuntimeError("Unexpected state - cast_to is `APIResponse`")
+
+        if inspect.isclass(origin) and issubclass(origin, httpx.Response):
+            # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
+            # and pass that class to our request functions. We cannot change the variance to be either
+            # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
+            # the response class ourselves but that is something that should be supported directly in httpx
+            # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
+            if cast_to != httpx.Response:
+                raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
+            return cast(R, response)
+
+        if (
+            inspect.isclass(
+                origin  # pyright: ignore[reportUnknownArgumentType]
+            )
+            and not issubclass(origin, BaseModel)
+            and issubclass(origin, pydantic.BaseModel)
+        ):
+            raise TypeError("Pydantic models must subclass our base model type, e.g. `from gitpod import BaseModel`")
+
+        if (
+            cast_to is not object
+            and not origin is list
+            and not origin is dict
+            and not origin is Union
+            and not issubclass(origin, BaseModel)
+        ):
+            raise RuntimeError(
+                f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
+            )
+
+        # split is required to handle cases where additional information is included
+        # in the response, e.g. application/json; charset=utf-8
+        content_type, *_ = response.headers.get("content-type", "*").split(";")
+        if not content_type.endswith("json"):
+            if is_basemodel(cast_to):
+                try:
+                    data = response.json()
+                except Exception as exc:
+                    log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
+                else:
+                    return self._client._process_response_data(
+                        data=data,
+                        cast_to=cast_to,  # type: ignore
+                        response=response,
+                    )
+
+            if self._client._strict_response_validation:
+                raise APIResponseValidationError(
+                    response=response,
+                    message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
+                    body=response.text,
+                )
+
+            # If the API responds with content that isn't JSON then we just return
+            # the (decoded) text without performing any parsing so that you can still
+            # handle the response however you need to.
+            return response.text  # type: ignore
+
+        data = response.json()
+
+        return self._client._process_response_data(
+            data=data,
+            cast_to=cast_to,  # type: ignore
+            response=response,
+        )
+
+
+class APIResponse(BaseAPIResponse[R]):
+    @overload
+    def parse(self, *, to: type[_T]) -> _T: ...
+
+    @overload
+    def parse(self) -> R: ...
+
+    def parse(self, *, to: type[_T] | None = None) -> R | _T:
+        """Returns the rich python representation of this response's data.
+
+        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
+
+        You can customise the type that the response is parsed into through
+        the `to` argument, e.g.
+
+        ```py
+        from gitpod import BaseModel
+
+
+        class MyModel(BaseModel):
+            foo: str
+
+
+        obj = response.parse(to=MyModel)
+        print(obj.foo)
+        ```
+
+        We support parsing:
+          - `BaseModel`
+          - `dict`
+          - `list`
+          - `Union`
+          - `str`
+          - `int`
+          - `float`
+          - `httpx.Response`
+        """
+        cache_key = to if to is not None else self._cast_to
+        cached = self._parsed_by_type.get(cache_key)
+        if cached is not None:
+            return cached  # type: ignore[no-any-return]
+
+        if not self._is_sse_stream:
+            self.read()
+
+        parsed = self._parse(to=to)
+        if is_given(self._options.post_parser):
+            parsed = self._options.post_parser(parsed)
+
+        self._parsed_by_type[cache_key] = parsed
+        return parsed
+
+    def read(self) -> bytes:
+        """Read and return the binary response content."""
+        try:
+            return self.http_response.read()
+        except httpx.StreamConsumed as exc:
+            # The default error raised by httpx isn't very
+            # helpful in our case so we re-raise it with
+            # a different error message.
+            raise StreamAlreadyConsumed() from exc
+
+    def text(self) -> str:
+        """Read and decode the response content into a string."""
+        self.read()
+        return self.http_response.text
+
+    def json(self) -> object:
+        """Read and decode the JSON response content."""
+        self.read()
+        return self.http_response.json()
+
+    def close(self) -> None:
+        """Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        self.http_response.close()
+
+    def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
+        """
+        A byte-iterator over the decoded response content.
+
+        This automatically handles gzip, deflate and brotli encoded responses.
+        """
+        for chunk in self.http_response.iter_bytes(chunk_size):
+            yield chunk
+
+    def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
+        """A str-iterator over the decoded response content
+        that handles both gzip, deflate, etc but also detects the content's
+        string encoding.
+        """
+        for chunk in self.http_response.iter_text(chunk_size):
+            yield chunk
+
+    def iter_lines(self) -> Iterator[str]:
+        """Like `iter_text()` but will only yield chunks for each line"""
+        for chunk in self.http_response.iter_lines():
+            yield chunk
+
+
+class AsyncAPIResponse(BaseAPIResponse[R]):
+    @overload
+    async def parse(self, *, to: type[_T]) -> _T: ...
+
+    @overload
+    async def parse(self) -> R: ...
+
+    async def parse(self, *, to: type[_T] | None = None) -> R | _T:
+        """Returns the rich python representation of this response's data.
+
+        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
+
+        You can customise the type that the response is parsed into through
+        the `to` argument, e.g.
+
+        ```py
+        from gitpod import BaseModel
+
+
+        class MyModel(BaseModel):
+            foo: str
+
+
+        obj = response.parse(to=MyModel)
+        print(obj.foo)
+        ```
+
+        We support parsing:
+          - `BaseModel`
+          - `dict`
+          - `list`
+          - `Union`
+          - `str`
+          - `httpx.Response`
+        """
+        cache_key = to if to is not None else self._cast_to
+        cached = self._parsed_by_type.get(cache_key)
+        if cached is not None:
+            return cached  # type: ignore[no-any-return]
+
+        if not self._is_sse_stream:
+            await self.read()
+
+        parsed = self._parse(to=to)
+        if is_given(self._options.post_parser):
+            parsed = self._options.post_parser(parsed)
+
+        self._parsed_by_type[cache_key] = parsed
+        return parsed
+
+    async def read(self) -> bytes:
+        """Read and return the binary response content."""
+        try:
+            return await self.http_response.aread()
+        except httpx.StreamConsumed as exc:
+            # the default error raised by httpx isn't very
+            # helpful in our case so we re-raise it with
+            # a different error message
+            raise StreamAlreadyConsumed() from exc
+
+    async def text(self) -> str:
+        """Read and decode the response content into a string."""
+        await self.read()
+        return self.http_response.text
+
+    async def json(self) -> object:
+        """Read and decode the JSON response content."""
+        await self.read()
+        return self.http_response.json()
+
+    async def close(self) -> None:
+        """Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        await self.http_response.aclose()
+
+    async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
+        """
+        A byte-iterator over the decoded response content.
+
+        This automatically handles gzip, deflate and brotli encoded responses.
+        """
+        async for chunk in self.http_response.aiter_bytes(chunk_size):
+            yield chunk
+
+    async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
+        """A str-iterator over the decoded response content
+        that handles both gzip, deflate, etc but also detects the content's
+        string encoding.
+        """
+        async for chunk in self.http_response.aiter_text(chunk_size):
+            yield chunk
+
+    async def iter_lines(self) -> AsyncIterator[str]:
+        """Like `iter_text()` but will only yield chunks for each line"""
+        async for chunk in self.http_response.aiter_lines():
+            yield chunk
+
+
+class BinaryAPIResponse(APIResponse[bytes]):
+    """Subclass of APIResponse providing helpers for dealing with binary data.
+
+    Note: If you want to stream the response data instead of eagerly reading it
+    all at once then you should use `.with_streaming_response` when making
+    the API request, e.g. `.with_streaming_response.get_binary_response()`
+    """
+
+    def write_to_file(
+        self,
+        file: str | os.PathLike[str],
+    ) -> None:
+        """Write the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+
+        Note: if you want to stream the data to the file instead of writing
+        all at once then you should use `.with_streaming_response` when making
+        the API request, e.g. `.with_streaming_response.get_binary_response()`
+        """
+        with open(file, mode="wb") as f:
+            for data in self.iter_bytes():
+                f.write(data)
+
+
+class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]):
+    """Subclass of APIResponse providing helpers for dealing with binary data.
+
+    Note: If you want to stream the response data instead of eagerly reading it
+    all at once then you should use `.with_streaming_response` when making
+    the API request, e.g. `.with_streaming_response.get_binary_response()`
+    """
+
+    async def write_to_file(
+        self,
+        file: str | os.PathLike[str],
+    ) -> None:
+        """Write the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+
+        Note: if you want to stream the data to the file instead of writing
+        all at once then you should use `.with_streaming_response` when making
+        the API request, e.g. `.with_streaming_response.get_binary_response()`
+        """
+        path = anyio.Path(file)
+        async with await path.open(mode="wb") as f:
+            async for data in self.iter_bytes():
+                await f.write(data)
+
+
+class StreamedBinaryAPIResponse(APIResponse[bytes]):
+    def stream_to_file(
+        self,
+        file: str | os.PathLike[str],
+        *,
+        chunk_size: int | None = None,
+    ) -> None:
+        """Streams the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+        """
+        with open(file, mode="wb") as f:
+            for data in self.iter_bytes(chunk_size):
+                f.write(data)
+
+
+class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]):
+    async def stream_to_file(
+        self,
+        file: str | os.PathLike[str],
+        *,
+        chunk_size: int | None = None,
+    ) -> None:
+        """Streams the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+        """
+        path = anyio.Path(file)
+        async with await path.open(mode="wb") as f:
+            async for data in self.iter_bytes(chunk_size):
+                await f.write(data)
+
+
+class MissingStreamClassError(TypeError):
+    def __init__(self) -> None:
+        super().__init__(
+            "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `gitpod._streaming` for reference",
+        )
+
+
+class StreamAlreadyConsumed(GitpodError):
+    """
+    Attempted to read or stream content, but the content has already
+    been streamed.
+
+    This can happen if you use a method like `.iter_lines()` and then attempt
+    to read th entire response body afterwards, e.g.
+
+    ```py
+    response = await client.post(...)
+    async for line in response.iter_lines():
+        ...  # do something with `line`
+
+    content = await response.read()
+    # ^ error
+    ```
+
+    If you want this behaviour you'll need to either manually accumulate the response
+    content or call `await response.read()` before iterating over the stream.
+    """
+
+    def __init__(self) -> None:
+        message = (
+            "Attempted to read or stream some content, but the content has "
+            "already been streamed. "
+            "This could be due to attempting to stream the response "
+            "content more than once."
+            "\n\n"
+            "You can fix this by manually accumulating the response content while streaming "
+            "or by calling `.read()` before starting to stream."
+        )
+        super().__init__(message)
+
+
+class ResponseContextManager(Generic[_APIResponseT]):
+    """Context manager for ensuring that a request is not made
+    until it is entered and that the response will always be closed
+    when the context manager exits
+    """
+
+    def __init__(self, request_func: Callable[[], _APIResponseT]) -> None:
+        self._request_func = request_func
+        self.__response: _APIResponseT | None = None
+
+    def __enter__(self) -> _APIResponseT:
+        self.__response = self._request_func()
+        return self.__response
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        if self.__response is not None:
+            self.__response.close()
+
+
+class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]):
+    """Context manager for ensuring that a request is not made
+    until it is entered and that the response will always be closed
+    when the context manager exits
+    """
+
+    def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None:
+        self._api_request = api_request
+        self.__response: _AsyncAPIResponseT | None = None
+
+    async def __aenter__(self) -> _AsyncAPIResponseT:
+        self.__response = await self._api_request
+        return self.__response
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        if self.__response is not None:
+            await self.__response.close()
+
+
+def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support streaming and returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = functools.partial(func, *args, **kwargs)
+
+        return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request))
+
+    return wrapped
+
+
+def async_to_streamed_response_wrapper(
+    func: Callable[P, Awaitable[R]],
+) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support streaming and returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = func(*args, **kwargs)
+
+        return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request))
+
+    return wrapped
+
+
+def to_custom_streamed_response_wrapper(
+    func: Callable[P, object],
+    response_cls: type[_APIResponseT],
+) -> Callable[P, ResponseContextManager[_APIResponseT]]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support streaming and returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = functools.partial(func, *args, **kwargs)
+
+        return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request))
+
+    return wrapped
+
+
+def async_to_custom_streamed_response_wrapper(
+    func: Callable[P, Awaitable[object]],
+    response_cls: type[_AsyncAPIResponseT],
+) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support streaming and returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = func(*args, **kwargs)
+
+        return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request))
+
+    return wrapped
+
+
+def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(APIResponse[R], func(*args, **kwargs))
+
+    return wrapped
+
+
+def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(AsyncAPIResponse[R], await func(*args, **kwargs))
+
+    return wrapped
+
+
+def to_custom_raw_response_wrapper(
+    func: Callable[P, object],
+    response_cls: type[_APIResponseT],
+) -> Callable[P, _APIResponseT]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(_APIResponseT, func(*args, **kwargs))
+
+    return wrapped
+
+
+def async_to_custom_raw_response_wrapper(
+    func: Callable[P, Awaitable[object]],
+    response_cls: type[_AsyncAPIResponseT],
+) -> Callable[P, Awaitable[_AsyncAPIResponseT]]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs))
+
+    return wrapped
+
+
+def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type:
+    """Given a type like `APIResponse[T]`, returns the generic type variable `T`.
+
+    This also handles the case where a concrete subclass is given, e.g.
+    ```py
+    class MyResponse(APIResponse[bytes]):
+        ...
+
+    extract_response_type(MyResponse) -> bytes
+    ```
+    """
+    return extract_type_var_from_base(
+        typ,
+        generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)),
+        index=0,
+    )
diff --git a/src/gitpod/_streaming.py b/src/gitpod/_streaming.py
new file mode 100644
index 0000000..deacb4d
--- /dev/null
+++ b/src/gitpod/_streaming.py
@@ -0,0 +1,333 @@
+# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py
+from __future__ import annotations
+
+import json
+import inspect
+from types import TracebackType
+from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast
+from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable
+
+import httpx
+
+from ._utils import extract_type_var_from_base
+
+if TYPE_CHECKING:
+    from ._client import Gitpod, AsyncGitpod
+
+
+_T = TypeVar("_T")
+
+
+class Stream(Generic[_T]):
+    """Provides the core interface to iterate over a synchronous stream response."""
+
+    response: httpx.Response
+
+    _decoder: SSEBytesDecoder
+
+    def __init__(
+        self,
+        *,
+        cast_to: type[_T],
+        response: httpx.Response,
+        client: Gitpod,
+    ) -> None:
+        self.response = response
+        self._cast_to = cast_to
+        self._client = client
+        self._decoder = client._make_sse_decoder()
+        self._iterator = self.__stream__()
+
+    def __next__(self) -> _T:
+        return self._iterator.__next__()
+
+    def __iter__(self) -> Iterator[_T]:
+        for item in self._iterator:
+            yield item
+
+    def _iter_events(self) -> Iterator[ServerSentEvent]:
+        yield from self._decoder.iter_bytes(self.response.iter_bytes())
+
+    def __stream__(self) -> Iterator[_T]:
+        cast_to = cast(Any, self._cast_to)
+        response = self.response
+        process_data = self._client._process_response_data
+        iterator = self._iter_events()
+
+        for sse in iterator:
+            yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+
+        # Ensure the entire stream is consumed
+        for _sse in iterator:
+            ...
+
+    def __enter__(self) -> Self:
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.close()
+
+    def close(self) -> None:
+        """
+        Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        self.response.close()
+
+
+class AsyncStream(Generic[_T]):
+    """Provides the core interface to iterate over an asynchronous stream response."""
+
+    response: httpx.Response
+
+    _decoder: SSEDecoder | SSEBytesDecoder
+
+    def __init__(
+        self,
+        *,
+        cast_to: type[_T],
+        response: httpx.Response,
+        client: AsyncGitpod,
+    ) -> None:
+        self.response = response
+        self._cast_to = cast_to
+        self._client = client
+        self._decoder = client._make_sse_decoder()
+        self._iterator = self.__stream__()
+
+    async def __anext__(self) -> _T:
+        return await self._iterator.__anext__()
+
+    async def __aiter__(self) -> AsyncIterator[_T]:
+        async for item in self._iterator:
+            yield item
+
+    async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
+        async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()):
+            yield sse
+
+    async def __stream__(self) -> AsyncIterator[_T]:
+        cast_to = cast(Any, self._cast_to)
+        response = self.response
+        process_data = self._client._process_response_data
+        iterator = self._iter_events()
+
+        async for sse in iterator:
+            yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+
+        # Ensure the entire stream is consumed
+        async for _sse in iterator:
+            ...
+
+    async def __aenter__(self) -> Self:
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        await self.close()
+
+    async def close(self) -> None:
+        """
+        Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        await self.response.aclose()
+
+
+class ServerSentEvent:
+    def __init__(
+        self,
+        *,
+        event: str | None = None,
+        data: str | None = None,
+        id: str | None = None,
+        retry: int | None = None,
+    ) -> None:
+        if data is None:
+            data = ""
+
+        self._id = id
+        self._data = data
+        self._event = event or None
+        self._retry = retry
+
+    @property
+    def event(self) -> str | None:
+        return self._event
+
+    @property
+    def id(self) -> str | None:
+        return self._id
+
+    @property
+    def retry(self) -> int | None:
+        return self._retry
+
+    @property
+    def data(self) -> str:
+        return self._data
+
+    def json(self) -> Any:
+        return json.loads(self.data)
+
+    @override
+    def __repr__(self) -> str:
+        return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})"
+
+
+class SSEDecoder:
+    _data: list[str]
+    _event: str | None
+    _retry: int | None
+    _last_event_id: str | None
+
+    def __init__(self) -> None:
+        self._event = None
+        self._data = []
+        self._last_event_id = None
+        self._retry = None
+
+    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
+        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        for chunk in self._iter_chunks(iterator):
+            # Split before decoding so splitlines() only uses \r and \n
+            for raw_line in chunk.splitlines():
+                line = raw_line.decode("utf-8")
+                sse = self.decode(line)
+                if sse:
+                    yield sse
+
+    def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]:
+        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
+        data = b""
+        for chunk in iterator:
+            for line in chunk.splitlines(keepends=True):
+                data += line
+                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
+                    yield data
+                    data = b""
+        if data:
+            yield data
+
+    async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
+        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        async for chunk in self._aiter_chunks(iterator):
+            # Split before decoding so splitlines() only uses \r and \n
+            for raw_line in chunk.splitlines():
+                line = raw_line.decode("utf-8")
+                sse = self.decode(line)
+                if sse:
+                    yield sse
+
+    async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
+        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
+        data = b""
+        async for chunk in iterator:
+            for line in chunk.splitlines(keepends=True):
+                data += line
+                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
+                    yield data
+                    data = b""
+        if data:
+            yield data
+
+    def decode(self, line: str) -> ServerSentEvent | None:
+        # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation  # noqa: E501
+
+        if not line:
+            if not self._event and not self._data and not self._last_event_id and self._retry is None:
+                return None
+
+            sse = ServerSentEvent(
+                event=self._event,
+                data="\n".join(self._data),
+                id=self._last_event_id,
+                retry=self._retry,
+            )
+
+            # NOTE: as per the SSE spec, do not reset last_event_id.
+            self._event = None
+            self._data = []
+            self._retry = None
+
+            return sse
+
+        if line.startswith(":"):
+            return None
+
+        fieldname, _, value = line.partition(":")
+
+        if value.startswith(" "):
+            value = value[1:]
+
+        if fieldname == "event":
+            self._event = value
+        elif fieldname == "data":
+            self._data.append(value)
+        elif fieldname == "id":
+            if "\0" in value:
+                pass
+            else:
+                self._last_event_id = value
+        elif fieldname == "retry":
+            try:
+                self._retry = int(value)
+            except (TypeError, ValueError):
+                pass
+        else:
+            pass  # Field is ignored.
+
+        return None
+
+
+@runtime_checkable
+class SSEBytesDecoder(Protocol):
+    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
+        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        ...
+
+    def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
+        """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        ...
+
+
+def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]:
+    """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`"""
+    origin = get_origin(typ) or typ
+    return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream))
+
+
+def extract_stream_chunk_type(
+    stream_cls: type,
+    *,
+    failure_message: str | None = None,
+) -> type:
+    """Given a type like `Stream[T]`, returns the generic type variable `T`.
+
+    This also handles the case where a concrete subclass is given, e.g.
+    ```py
+    class MyStream(Stream[bytes]):
+        ...
+
+    extract_stream_chunk_type(MyStream) -> bytes
+    ```
+    """
+    from ._base_client import Stream, AsyncStream
+
+    return extract_type_var_from_base(
+        stream_cls,
+        index=0,
+        generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)),
+        failure_message=failure_message,
+    )
diff --git a/src/gitpod/_types.py b/src/gitpod/_types.py
new file mode 100644
index 0000000..b5cea32
--- /dev/null
+++ b/src/gitpod/_types.py
@@ -0,0 +1,219 @@
+from __future__ import annotations
+
+from os import PathLike
+from typing import (
+    IO,
+    TYPE_CHECKING,
+    Any,
+    Dict,
+    List,
+    Type,
+    Tuple,
+    Union,
+    Mapping,
+    TypeVar,
+    Callable,
+    Optional,
+    Sequence,
+)
+from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable
+
+import httpx
+import pydantic
+from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport
+
+if TYPE_CHECKING:
+    from ._models import BaseModel
+    from ._response import APIResponse, AsyncAPIResponse
+
+Transport = BaseTransport
+AsyncTransport = AsyncBaseTransport
+Query = Mapping[str, object]
+Body = object
+AnyMapping = Mapping[str, object]
+ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
+_T = TypeVar("_T")
+
+
+# Approximates httpx internal ProxiesTypes and RequestFiles types
+# while adding support for `PathLike` instances
+ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
+ProxiesTypes = Union[str, Proxy, ProxiesDict]
+if TYPE_CHECKING:
+    Base64FileInput = Union[IO[bytes], PathLike[str]]
+    FileContent = Union[IO[bytes], bytes, PathLike[str]]
+else:
+    Base64FileInput = Union[IO[bytes], PathLike]
+    FileContent = Union[IO[bytes], bytes, PathLike]  # PathLike is not subscriptable in Python 3.8.
+FileTypes = Union[
+    # file (or bytes)
+    FileContent,
+    # (filename, file (or bytes))
+    Tuple[Optional[str], FileContent],
+    # (filename, file (or bytes), content_type)
+    Tuple[Optional[str], FileContent, Optional[str]],
+    # (filename, file (or bytes), content_type, headers)
+    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
+]
+RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
+
+# duplicate of the above but without our custom file support
+HttpxFileContent = Union[IO[bytes], bytes]
+HttpxFileTypes = Union[
+    # file (or bytes)
+    HttpxFileContent,
+    # (filename, file (or bytes))
+    Tuple[Optional[str], HttpxFileContent],
+    # (filename, file (or bytes), content_type)
+    Tuple[Optional[str], HttpxFileContent, Optional[str]],
+    # (filename, file (or bytes), content_type, headers)
+    Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]],
+]
+HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]]
+
+# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT
+# where ResponseT includes `None`. In order to support directly
+# passing `None`, overloads would have to be defined for every
+# method that uses `ResponseT` which would lead to an unacceptable
+# amount of code duplication and make it unreadable. See _base_client.py
+# for example usage.
+#
+# This unfortunately means that you will either have
+# to import this type and pass it explicitly:
+#
+# from gitpod import NoneType
+# client.get('/foo', cast_to=NoneType)
+#
+# or build it yourself:
+#
+# client.get('/foo', cast_to=type(None))
+if TYPE_CHECKING:
+    NoneType: Type[None]
+else:
+    NoneType = type(None)
+
+
+class RequestOptions(TypedDict, total=False):
+    headers: Headers
+    max_retries: int
+    timeout: float | Timeout | None
+    params: Query
+    extra_json: AnyMapping
+    idempotency_key: str
+    follow_redirects: bool
+
+
+# Sentinel class used until PEP 0661 is accepted
+class NotGiven:
+    """
+    A sentinel singleton class used to distinguish omitted keyword arguments
+    from those passed in with the value None (which may have different behavior).
+
+    For example:
+
+    ```py
+    def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ...
+
+
+    get(timeout=1)  # 1s timeout
+    get(timeout=None)  # No timeout
+    get()  # Default timeout behavior, which may not be statically known at the method definition.
+    ```
+    """
+
+    def __bool__(self) -> Literal[False]:
+        return False
+
+    @override
+    def __repr__(self) -> str:
+        return "NOT_GIVEN"
+
+
+NotGivenOr = Union[_T, NotGiven]
+NOT_GIVEN = NotGiven()
+
+
+class Omit:
+    """In certain situations you need to be able to represent a case where a default value has
+    to be explicitly removed and `None` is not an appropriate substitute, for example:
+
+    ```py
+    # as the default `Content-Type` header is `application/json` that will be sent
+    client.post("/upload/files", files={"file": b"my raw file content"})
+
+    # you can't explicitly override the header as it has to be dynamically generated
+    # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
+    client.post(..., headers={"Content-Type": "multipart/form-data"})
+
+    # instead you can remove the default `application/json` header by passing Omit
+    client.post(..., headers={"Content-Type": Omit()})
+    ```
+    """
+
+    def __bool__(self) -> Literal[False]:
+        return False
+
+
+@runtime_checkable
+class ModelBuilderProtocol(Protocol):
+    @classmethod
+    def build(
+        cls: type[_T],
+        *,
+        response: Response,
+        data: object,
+    ) -> _T: ...
+
+
+Headers = Mapping[str, Union[str, Omit]]
+
+
+class HeadersLikeProtocol(Protocol):
+    def get(self, __key: str) -> str | None: ...
+
+
+HeadersLike = Union[Headers, HeadersLikeProtocol]
+
+ResponseT = TypeVar(
+    "ResponseT",
+    bound=Union[
+        object,
+        str,
+        None,
+        "BaseModel",
+        List[Any],
+        Dict[str, Any],
+        Response,
+        ModelBuilderProtocol,
+        "APIResponse[Any]",
+        "AsyncAPIResponse[Any]",
+    ],
+)
+
+StrBytesIntFloat = Union[str, bytes, int, float]
+
+# Note: copied from Pydantic
+# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79
+IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]]
+
+PostParser = Callable[[Any], Any]
+
+
+@runtime_checkable
+class InheritsGeneric(Protocol):
+    """Represents a type that has inherited from `Generic`
+
+    The `__orig_bases__` property can be used to determine the resolved
+    type variable for a given base class.
+    """
+
+    __orig_bases__: tuple[_GenericAlias]
+
+
+class _GenericAlias(Protocol):
+    __origin__: type[object]
+
+
+class HttpxSendArgs(TypedDict, total=False):
+    auth: httpx.Auth
+    follow_redirects: bool
diff --git a/src/gitpod/_utils/__init__.py b/src/gitpod/_utils/__init__.py
new file mode 100644
index 0000000..d4fda26
--- /dev/null
+++ b/src/gitpod/_utils/__init__.py
@@ -0,0 +1,57 @@
+from ._sync import asyncify as asyncify
+from ._proxy import LazyProxy as LazyProxy
+from ._utils import (
+    flatten as flatten,
+    is_dict as is_dict,
+    is_list as is_list,
+    is_given as is_given,
+    is_tuple as is_tuple,
+    json_safe as json_safe,
+    lru_cache as lru_cache,
+    is_mapping as is_mapping,
+    is_tuple_t as is_tuple_t,
+    parse_date as parse_date,
+    is_iterable as is_iterable,
+    is_sequence as is_sequence,
+    coerce_float as coerce_float,
+    is_mapping_t as is_mapping_t,
+    removeprefix as removeprefix,
+    removesuffix as removesuffix,
+    extract_files as extract_files,
+    is_sequence_t as is_sequence_t,
+    required_args as required_args,
+    coerce_boolean as coerce_boolean,
+    coerce_integer as coerce_integer,
+    file_from_path as file_from_path,
+    parse_datetime as parse_datetime,
+    strip_not_given as strip_not_given,
+    deepcopy_minimal as deepcopy_minimal,
+    get_async_library as get_async_library,
+    maybe_coerce_float as maybe_coerce_float,
+    get_required_header as get_required_header,
+    maybe_coerce_boolean as maybe_coerce_boolean,
+    maybe_coerce_integer as maybe_coerce_integer,
+)
+from ._typing import (
+    is_list_type as is_list_type,
+    is_union_type as is_union_type,
+    extract_type_arg as extract_type_arg,
+    is_iterable_type as is_iterable_type,
+    is_required_type as is_required_type,
+    is_annotated_type as is_annotated_type,
+    is_type_alias_type as is_type_alias_type,
+    strip_annotated_type as strip_annotated_type,
+    extract_type_var_from_base as extract_type_var_from_base,
+)
+from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator
+from ._transform import (
+    PropertyInfo as PropertyInfo,
+    transform as transform,
+    async_transform as async_transform,
+    maybe_transform as maybe_transform,
+    async_maybe_transform as async_maybe_transform,
+)
+from ._reflection import (
+    function_has_argument as function_has_argument,
+    assert_signatures_in_sync as assert_signatures_in_sync,
+)
diff --git a/src/gitpod/_utils/_logs.py b/src/gitpod/_utils/_logs.py
new file mode 100644
index 0000000..229b952
--- /dev/null
+++ b/src/gitpod/_utils/_logs.py
@@ -0,0 +1,25 @@
+import os
+import logging
+
+logger: logging.Logger = logging.getLogger("gitpod")
+httpx_logger: logging.Logger = logging.getLogger("httpx")
+
+
+def _basic_config() -> None:
+    # e.g. [2023-10-05 14:12:26 - gitpod._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK"
+    logging.basicConfig(
+        format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
+        datefmt="%Y-%m-%d %H:%M:%S",
+    )
+
+
+def setup_logging() -> None:
+    env = os.environ.get("GITPOD_LOG")
+    if env == "debug":
+        _basic_config()
+        logger.setLevel(logging.DEBUG)
+        httpx_logger.setLevel(logging.DEBUG)
+    elif env == "info":
+        _basic_config()
+        logger.setLevel(logging.INFO)
+        httpx_logger.setLevel(logging.INFO)
diff --git a/src/gitpod/_utils/_proxy.py b/src/gitpod/_utils/_proxy.py
new file mode 100644
index 0000000..0f239a3
--- /dev/null
+++ b/src/gitpod/_utils/_proxy.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import Generic, TypeVar, Iterable, cast
+from typing_extensions import override
+
+T = TypeVar("T")
+
+
+class LazyProxy(Generic[T], ABC):
+    """Implements data methods to pretend that an instance is another instance.
+
+    This includes forwarding attribute access and other methods.
+    """
+
+    # Note: we have to special case proxies that themselves return proxies
+    # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz`
+
+    def __getattr__(self, attr: str) -> object:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return proxied  # pyright: ignore
+        return getattr(proxied, attr)
+
+    @override
+    def __repr__(self) -> str:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return proxied.__class__.__name__
+        return repr(self.__get_proxied__())
+
+    @override
+    def __str__(self) -> str:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return proxied.__class__.__name__
+        return str(proxied)
+
+    @override
+    def __dir__(self) -> Iterable[str]:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return []
+        return proxied.__dir__()
+
+    @property  # type: ignore
+    @override
+    def __class__(self) -> type:  # pyright: ignore
+        try:
+            proxied = self.__get_proxied__()
+        except Exception:
+            return type(self)
+        if issubclass(type(proxied), LazyProxy):
+            return type(proxied)
+        return proxied.__class__
+
+    def __get_proxied__(self) -> T:
+        return self.__load__()
+
+    def __as_proxied__(self) -> T:
+        """Helper method that returns the current proxy, typed as the loaded object"""
+        return cast(T, self)
+
+    @abstractmethod
+    def __load__(self) -> T: ...
diff --git a/src/gitpod/_utils/_reflection.py b/src/gitpod/_utils/_reflection.py
new file mode 100644
index 0000000..89aa712
--- /dev/null
+++ b/src/gitpod/_utils/_reflection.py
@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+import inspect
+from typing import Any, Callable
+
+
+def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool:
+    """Returns whether or not the given function has a specific parameter"""
+    sig = inspect.signature(func)
+    return arg_name in sig.parameters
+
+
+def assert_signatures_in_sync(
+    source_func: Callable[..., Any],
+    check_func: Callable[..., Any],
+    *,
+    exclude_params: set[str] = set(),
+) -> None:
+    """Ensure that the signature of the second function matches the first."""
+
+    check_sig = inspect.signature(check_func)
+    source_sig = inspect.signature(source_func)
+
+    errors: list[str] = []
+
+    for name, source_param in source_sig.parameters.items():
+        if name in exclude_params:
+            continue
+
+        custom_param = check_sig.parameters.get(name)
+        if not custom_param:
+            errors.append(f"the `{name}` param is missing")
+            continue
+
+        if custom_param.annotation != source_param.annotation:
+            errors.append(
+                f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}"
+            )
+            continue
+
+    if errors:
+        raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors))
diff --git a/src/gitpod/_utils/_resources_proxy.py b/src/gitpod/_utils/_resources_proxy.py
new file mode 100644
index 0000000..7ffe977
--- /dev/null
+++ b/src/gitpod/_utils/_resources_proxy.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+from typing import Any
+from typing_extensions import override
+
+from ._proxy import LazyProxy
+
+
+class ResourcesProxy(LazyProxy[Any]):
+    """A proxy for the `gitpod.resources` module.
+
+    This is used so that we can lazily import `gitpod.resources` only when
+    needed *and* so that users can just import `gitpod` and reference `gitpod.resources`
+    """
+
+    @override
+    def __load__(self) -> Any:
+        import importlib
+
+        mod = importlib.import_module("gitpod.resources")
+        return mod
+
+
+resources = ResourcesProxy().__as_proxied__()
diff --git a/src/gitpod/_utils/_streams.py b/src/gitpod/_utils/_streams.py
new file mode 100644
index 0000000..f4a0208
--- /dev/null
+++ b/src/gitpod/_utils/_streams.py
@@ -0,0 +1,12 @@
+from typing import Any
+from typing_extensions import Iterator, AsyncIterator
+
+
+def consume_sync_iterator(iterator: Iterator[Any]) -> None:
+    for _ in iterator:
+        ...
+
+
+async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
+    async for _ in iterator:
+        ...
diff --git a/src/gitpod/_utils/_sync.py b/src/gitpod/_utils/_sync.py
new file mode 100644
index 0000000..ad7ec71
--- /dev/null
+++ b/src/gitpod/_utils/_sync.py
@@ -0,0 +1,86 @@
+from __future__ import annotations
+
+import sys
+import asyncio
+import functools
+import contextvars
+from typing import Any, TypeVar, Callable, Awaitable
+from typing_extensions import ParamSpec
+
+import anyio
+import sniffio
+import anyio.to_thread
+
+T_Retval = TypeVar("T_Retval")
+T_ParamSpec = ParamSpec("T_ParamSpec")
+
+
+if sys.version_info >= (3, 9):
+    _asyncio_to_thread = asyncio.to_thread
+else:
+    # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
+    # for Python 3.8 support
+    async def _asyncio_to_thread(
+        func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
+    ) -> Any:
+        """Asynchronously run function *func* in a separate thread.
+
+        Any *args and **kwargs supplied for this function are directly passed
+        to *func*. Also, the current :class:`contextvars.Context` is propagated,
+        allowing context variables from the main thread to be accessed in the
+        separate thread.
+
+        Returns a coroutine that can be awaited to get the eventual result of *func*.
+        """
+        loop = asyncio.events.get_running_loop()
+        ctx = contextvars.copy_context()
+        func_call = functools.partial(ctx.run, func, *args, **kwargs)
+        return await loop.run_in_executor(None, func_call)
+
+
+async def to_thread(
+    func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
+) -> T_Retval:
+    if sniffio.current_async_library() == "asyncio":
+        return await _asyncio_to_thread(func, *args, **kwargs)
+
+    return await anyio.to_thread.run_sync(
+        functools.partial(func, *args, **kwargs),
+    )
+
+
+# inspired by `asyncer`, https://github.com/tiangolo/asyncer
+def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
+    """
+    Take a blocking function and create an async one that receives the same
+    positional and keyword arguments. For python version 3.9 and above, it uses
+    asyncio.to_thread to run the function in a separate thread. For python version
+    3.8, it uses locally defined copy of the asyncio.to_thread function which was
+    introduced in python 3.9.
+
+    Usage:
+
+    ```python
+    def blocking_func(arg1, arg2, kwarg1=None):
+        # blocking code
+        return result
+
+
+    result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1)
+    ```
+
+    ## Arguments
+
+    `function`: a blocking regular callable (e.g. a function)
+
+    ## Return
+
+    An async function that takes the same positional and keyword arguments as the
+    original one, that when called runs the same original function in a thread worker
+    and returns the result.
+    """
+
+    async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
+        return await to_thread(function, *args, **kwargs)
+
+    return wrapper
diff --git a/src/gitpod/_utils/_transform.py b/src/gitpod/_utils/_transform.py
new file mode 100644
index 0000000..b0cc20a
--- /dev/null
+++ b/src/gitpod/_utils/_transform.py
@@ -0,0 +1,447 @@
+from __future__ import annotations
+
+import io
+import base64
+import pathlib
+from typing import Any, Mapping, TypeVar, cast
+from datetime import date, datetime
+from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints
+
+import anyio
+import pydantic
+
+from ._utils import (
+    is_list,
+    is_given,
+    lru_cache,
+    is_mapping,
+    is_iterable,
+)
+from .._files import is_base64_file_input
+from ._typing import (
+    is_list_type,
+    is_union_type,
+    extract_type_arg,
+    is_iterable_type,
+    is_required_type,
+    is_annotated_type,
+    strip_annotated_type,
+)
+from .._compat import get_origin, model_dump, is_typeddict
+
+_T = TypeVar("_T")
+
+
+# TODO: support for drilling globals() and locals()
+# TODO: ensure works correctly with forward references in all cases
+
+
+PropertyFormat = Literal["iso8601", "base64", "custom"]
+
+
+class PropertyInfo:
+    """Metadata class to be used in Annotated types to provide information about a given type.
+
+    For example:
+
+    class MyParams(TypedDict):
+        account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')]
+
+    This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API.
+    """
+
+    alias: str | None
+    format: PropertyFormat | None
+    format_template: str | None
+    discriminator: str | None
+
+    def __init__(
+        self,
+        *,
+        alias: str | None = None,
+        format: PropertyFormat | None = None,
+        format_template: str | None = None,
+        discriminator: str | None = None,
+    ) -> None:
+        self.alias = alias
+        self.format = format
+        self.format_template = format_template
+        self.discriminator = discriminator
+
+    @override
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')"
+
+
+def maybe_transform(
+    data: object,
+    expected_type: object,
+) -> Any | None:
+    """Wrapper over `transform()` that allows `None` to be passed.
+
+    See `transform()` for more details.
+    """
+    if data is None:
+        return None
+    return transform(data, expected_type)
+
+
+# Wrapper over _transform_recursive providing fake types
+def transform(
+    data: _T,
+    expected_type: object,
+) -> _T:
+    """Transform dictionaries based off of type information from the given type, for example:
+
+    ```py
+    class Params(TypedDict, total=False):
+        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
+
+
+    transformed = transform({"card_id": "<my card ID>"}, Params)
+    # {'cardID': '<my card ID>'}
+    ```
+
+    Any keys / data that does not have type information given will be included as is.
+
+    It should be noted that the transformations that this function does are not represented in the type system.
+    """
+    transformed = _transform_recursive(data, annotation=cast(type, expected_type))
+    return cast(_T, transformed)
+
+
+@lru_cache(maxsize=8096)
+def _get_annotated_type(type_: type) -> type | None:
+    """If the given type is an `Annotated` type then it is returned, if not `None` is returned.
+
+    This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]`
+    """
+    if is_required_type(type_):
+        # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]`
+        type_ = get_args(type_)[0]
+
+    if is_annotated_type(type_):
+        return type_
+
+    return None
+
+
+def _maybe_transform_key(key: str, type_: type) -> str:
+    """Transform the given `data` based on the annotations provided in `type_`.
+
+    Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
+    """
+    annotated_type = _get_annotated_type(type_)
+    if annotated_type is None:
+        # no `Annotated` definition for this type, no transformation needed
+        return key
+
+    # ignore the first argument as it is the actual type
+    annotations = get_args(annotated_type)[1:]
+    for annotation in annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.alias is not None:
+            return annotation.alias
+
+    return key
+
+
+def _no_transform_needed(annotation: type) -> bool:
+    return annotation == float or annotation == int
+
+
+def _transform_recursive(
+    data: object,
+    *,
+    annotation: type,
+    inner_type: type | None = None,
+) -> object:
+    """Transform the given data against the expected type.
+
+    Args:
+        annotation: The direct type annotation given to the particular piece of data.
+            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc
+
+        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
+            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
+            the list can be transformed using the metadata from the container type.
+
+            Defaults to the same value as the `annotation` argument.
+    """
+    if inner_type is None:
+        inner_type = annotation
+
+    stripped_type = strip_annotated_type(inner_type)
+    origin = get_origin(stripped_type) or stripped_type
+    if is_typeddict(stripped_type) and is_mapping(data):
+        return _transform_typeddict(data, stripped_type)
+
+    if origin == dict and is_mapping(data):
+        items_type = get_args(stripped_type)[1]
+        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
+
+    if (
+        # List[T]
+        (is_list_type(stripped_type) and is_list(data))
+        # Iterable[T]
+        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
+    ):
+        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
+        # intended as an iterable, so we don't transform it.
+        if isinstance(data, dict):
+            return cast(object, data)
+
+        inner_type = extract_type_arg(stripped_type, 0)
+        if _no_transform_needed(inner_type):
+            # for some types there is no need to transform anything, so we can get a small
+            # perf boost from skipping that work.
+            #
+            # but we still need to convert to a list to ensure the data is json-serializable
+            if is_list(data):
+                return data
+            return list(data)
+
+        return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
+
+    if is_union_type(stripped_type):
+        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
+        #
+        # TODO: there may be edge cases where the same normalized field name will transform to two different names
+        # in different subtypes.
+        for subtype in get_args(stripped_type):
+            data = _transform_recursive(data, annotation=annotation, inner_type=subtype)
+        return data
+
+    if isinstance(data, pydantic.BaseModel):
+        return model_dump(data, exclude_unset=True, mode="json")
+
+    annotated_type = _get_annotated_type(annotation)
+    if annotated_type is None:
+        return data
+
+    # ignore the first argument as it is the actual type
+    annotations = get_args(annotated_type)[1:]
+    for annotation in annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
+            return _format_data(data, annotation.format, annotation.format_template)
+
+    return data
+
+
+def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
+    if isinstance(data, (date, datetime)):
+        if format_ == "iso8601":
+            return data.isoformat()
+
+        if format_ == "custom" and format_template is not None:
+            return data.strftime(format_template)
+
+    if format_ == "base64" and is_base64_file_input(data):
+        binary: str | bytes | None = None
+
+        if isinstance(data, pathlib.Path):
+            binary = data.read_bytes()
+        elif isinstance(data, io.IOBase):
+            binary = data.read()
+
+            if isinstance(binary, str):  # type: ignore[unreachable]
+                binary = binary.encode()
+
+        if not isinstance(binary, bytes):
+            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")
+
+        return base64.b64encode(binary).decode("ascii")
+
+    return data
+
+
+def _transform_typeddict(
+    data: Mapping[str, object],
+    expected_type: type,
+) -> Mapping[str, object]:
+    result: dict[str, object] = {}
+    annotations = get_type_hints(expected_type, include_extras=True)
+    for key, value in data.items():
+        if not is_given(value):
+            # we don't need to include `NotGiven` values here as they'll
+            # be stripped out before the request is sent anyway
+            continue
+
+        type_ = annotations.get(key)
+        if type_ is None:
+            # we do not have a type annotation for this field, leave it as is
+            result[key] = value
+        else:
+            result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_)
+    return result
+
+
+async def async_maybe_transform(
+    data: object,
+    expected_type: object,
+) -> Any | None:
+    """Wrapper over `async_transform()` that allows `None` to be passed.
+
+    See `async_transform()` for more details.
+    """
+    if data is None:
+        return None
+    return await async_transform(data, expected_type)
+
+
+async def async_transform(
+    data: _T,
+    expected_type: object,
+) -> _T:
+    """Transform dictionaries based off of type information from the given type, for example:
+
+    ```py
+    class Params(TypedDict, total=False):
+        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
+
+
+    transformed = transform({"card_id": "<my card ID>"}, Params)
+    # {'cardID': '<my card ID>'}
+    ```
+
+    Any keys / data that does not have type information given will be included as is.
+
+    It should be noted that the transformations that this function does are not represented in the type system.
+    """
+    transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type))
+    return cast(_T, transformed)
+
+
+async def _async_transform_recursive(
+    data: object,
+    *,
+    annotation: type,
+    inner_type: type | None = None,
+) -> object:
+    """Transform the given data against the expected type.
+
+    Args:
+        annotation: The direct type annotation given to the particular piece of data.
+            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc
+
+        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
+            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
+            the list can be transformed using the metadata from the container type.
+
+            Defaults to the same value as the `annotation` argument.
+    """
+    if inner_type is None:
+        inner_type = annotation
+
+    stripped_type = strip_annotated_type(inner_type)
+    origin = get_origin(stripped_type) or stripped_type
+    if is_typeddict(stripped_type) and is_mapping(data):
+        return await _async_transform_typeddict(data, stripped_type)
+
+    if origin == dict and is_mapping(data):
+        items_type = get_args(stripped_type)[1]
+        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
+
+    if (
+        # List[T]
+        (is_list_type(stripped_type) and is_list(data))
+        # Iterable[T]
+        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
+    ):
+        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
+        # intended as an iterable, so we don't transform it.
+        if isinstance(data, dict):
+            return cast(object, data)
+
+        inner_type = extract_type_arg(stripped_type, 0)
+        if _no_transform_needed(inner_type):
+            # for some types there is no need to transform anything, so we can get a small
+            # perf boost from skipping that work.
+            #
+            # but we still need to convert to a list to ensure the data is json-serializable
+            if is_list(data):
+                return data
+            return list(data)
+
+        return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
+
+    if is_union_type(stripped_type):
+        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
+        #
+        # TODO: there may be edge cases where the same normalized field name will transform to two different names
+        # in different subtypes.
+        for subtype in get_args(stripped_type):
+            data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype)
+        return data
+
+    if isinstance(data, pydantic.BaseModel):
+        return model_dump(data, exclude_unset=True, mode="json")
+
+    annotated_type = _get_annotated_type(annotation)
+    if annotated_type is None:
+        return data
+
+    # ignore the first argument as it is the actual type
+    annotations = get_args(annotated_type)[1:]
+    for annotation in annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
+            return await _async_format_data(data, annotation.format, annotation.format_template)
+
+    return data
+
+
+async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
+    if isinstance(data, (date, datetime)):
+        if format_ == "iso8601":
+            return data.isoformat()
+
+        if format_ == "custom" and format_template is not None:
+            return data.strftime(format_template)
+
+    if format_ == "base64" and is_base64_file_input(data):
+        binary: str | bytes | None = None
+
+        if isinstance(data, pathlib.Path):
+            binary = await anyio.Path(data).read_bytes()
+        elif isinstance(data, io.IOBase):
+            binary = data.read()
+
+            if isinstance(binary, str):  # type: ignore[unreachable]
+                binary = binary.encode()
+
+        if not isinstance(binary, bytes):
+            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")
+
+        return base64.b64encode(binary).decode("ascii")
+
+    return data
+
+
+async def _async_transform_typeddict(
+    data: Mapping[str, object],
+    expected_type: type,
+) -> Mapping[str, object]:
+    result: dict[str, object] = {}
+    annotations = get_type_hints(expected_type, include_extras=True)
+    for key, value in data.items():
+        if not is_given(value):
+            # we don't need to include `NotGiven` values here as they'll
+            # be stripped out before the request is sent anyway
+            continue
+
+        type_ = annotations.get(key)
+        if type_ is None:
+            # we do not have a type annotation for this field, leave it as is
+            result[key] = value
+        else:
+            result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
+    return result
+
+
+@lru_cache(maxsize=8096)
+def get_type_hints(
+    obj: Any,
+    globalns: dict[str, Any] | None = None,
+    localns: Mapping[str, Any] | None = None,
+    include_extras: bool = False,
+) -> dict[str, Any]:
+    return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
diff --git a/src/gitpod/_utils/_typing.py b/src/gitpod/_utils/_typing.py
new file mode 100644
index 0000000..1bac954
--- /dev/null
+++ b/src/gitpod/_utils/_typing.py
@@ -0,0 +1,151 @@
+from __future__ import annotations
+
+import sys
+import typing
+import typing_extensions
+from typing import Any, TypeVar, Iterable, cast
+from collections import abc as _c_abc
+from typing_extensions import (
+    TypeIs,
+    Required,
+    Annotated,
+    get_args,
+    get_origin,
+)
+
+from ._utils import lru_cache
+from .._types import InheritsGeneric
+from .._compat import is_union as _is_union
+
+
+def is_annotated_type(typ: type) -> bool:
+    return get_origin(typ) == Annotated
+
+
+def is_list_type(typ: type) -> bool:
+    return (get_origin(typ) or typ) == list
+
+
+def is_iterable_type(typ: type) -> bool:
+    """If the given type is `typing.Iterable[T]`"""
+    origin = get_origin(typ) or typ
+    return origin == Iterable or origin == _c_abc.Iterable
+
+
+def is_union_type(typ: type) -> bool:
+    return _is_union(get_origin(typ))
+
+
+def is_required_type(typ: type) -> bool:
+    return get_origin(typ) == Required
+
+
+def is_typevar(typ: type) -> bool:
+    # type ignore is required because type checkers
+    # think this expression will always return False
+    return type(typ) == TypeVar  # type: ignore
+
+
+_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,)
+if sys.version_info >= (3, 12):
+    _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType)
+
+
+def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
+    """Return whether the provided argument is an instance of `TypeAliasType`.
+
+    ```python
+    type Int = int
+    is_type_alias_type(Int)
+    # > True
+    Str = TypeAliasType("Str", str)
+    is_type_alias_type(Str)
+    # > True
+    ```
+    """
+    return isinstance(tp, _TYPE_ALIAS_TYPES)
+
+
+# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
+@lru_cache(maxsize=8096)
+def strip_annotated_type(typ: type) -> type:
+    if is_required_type(typ) or is_annotated_type(typ):
+        return strip_annotated_type(cast(type, get_args(typ)[0]))
+
+    return typ
+
+
+def extract_type_arg(typ: type, index: int) -> type:
+    args = get_args(typ)
+    try:
+        return cast(type, args[index])
+    except IndexError as err:
+        raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err
+
+
+def extract_type_var_from_base(
+    typ: type,
+    *,
+    generic_bases: tuple[type, ...],
+    index: int,
+    failure_message: str | None = None,
+) -> type:
+    """Given a type like `Foo[T]`, returns the generic type variable `T`.
+
+    This also handles the case where a concrete subclass is given, e.g.
+    ```py
+    class MyResponse(Foo[bytes]):
+        ...
+
+    extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes
+    ```
+
+    And where a generic subclass is given:
+    ```py
+    _T = TypeVar('_T')
+    class MyResponse(Foo[_T]):
+        ...
+
+    extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes
+    ```
+    """
+    cls = cast(object, get_origin(typ) or typ)
+    if cls in generic_bases:  # pyright: ignore[reportUnnecessaryContains]
+        # we're given the class directly
+        return extract_type_arg(typ, index)
+
+    # if a subclass is given
+    # ---
+    # this is needed as __orig_bases__ is not present in the typeshed stubs
+    # because it is intended to be for internal use only, however there does
+    # not seem to be a way to resolve generic TypeVars for inherited subclasses
+    # without using it.
+    if isinstance(cls, InheritsGeneric):
+        target_base_class: Any | None = None
+        for base in cls.__orig_bases__:
+            if base.__origin__ in generic_bases:
+                target_base_class = base
+                break
+
+        if target_base_class is None:
+            raise RuntimeError(
+                "Could not find the generic base class;\n"
+                "This should never happen;\n"
+                f"Does {cls} inherit from one of {generic_bases} ?"
+            )
+
+        extracted = extract_type_arg(target_base_class, index)
+        if is_typevar(extracted):
+            # If the extracted type argument is itself a type variable
+            # then that means the subclass itself is generic, so we have
+            # to resolve the type argument from the class itself, not
+            # the base class.
+            #
+            # Note: if there is more than 1 type argument, the subclass could
+            # change the ordering of the type arguments, this is not currently
+            # supported.
+            return extract_type_arg(typ, index)
+
+        return extracted
+
+    raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}")
diff --git a/src/gitpod/_utils/_utils.py b/src/gitpod/_utils/_utils.py
new file mode 100644
index 0000000..ea3cf3f
--- /dev/null
+++ b/src/gitpod/_utils/_utils.py
@@ -0,0 +1,422 @@
+from __future__ import annotations
+
+import os
+import re
+import inspect
+import functools
+from typing import (
+    Any,
+    Tuple,
+    Mapping,
+    TypeVar,
+    Callable,
+    Iterable,
+    Sequence,
+    cast,
+    overload,
+)
+from pathlib import Path
+from datetime import date, datetime
+from typing_extensions import TypeGuard
+
+import sniffio
+
+from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike
+from .._compat import parse_date as parse_date, parse_datetime as parse_datetime
+
+_T = TypeVar("_T")
+_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
+_MappingT = TypeVar("_MappingT", bound=Mapping[str, object])
+_SequenceT = TypeVar("_SequenceT", bound=Sequence[object])
+CallableT = TypeVar("CallableT", bound=Callable[..., Any])
+
+
+def flatten(t: Iterable[Iterable[_T]]) -> list[_T]:
+    return [item for sublist in t for item in sublist]
+
+
+def extract_files(
+    # TODO: this needs to take Dict but variance issues.....
+    # create protocol type ?
+    query: Mapping[str, object],
+    *,
+    paths: Sequence[Sequence[str]],
+) -> list[tuple[str, FileTypes]]:
+    """Recursively extract files from the given dictionary based on specified paths.
+
+    A path may look like this ['foo', 'files', '<array>', 'data'].
+
+    Note: this mutates the given dictionary.
+    """
+    files: list[tuple[str, FileTypes]] = []
+    for path in paths:
+        files.extend(_extract_items(query, path, index=0, flattened_key=None))
+    return files
+
+
+def _extract_items(
+    obj: object,
+    path: Sequence[str],
+    *,
+    index: int,
+    flattened_key: str | None,
+) -> list[tuple[str, FileTypes]]:
+    try:
+        key = path[index]
+    except IndexError:
+        if isinstance(obj, NotGiven):
+            # no value was provided - we can safely ignore
+            return []
+
+        # cyclical import
+        from .._files import assert_is_file_content
+
+        # We have exhausted the path, return the entry we found.
+        assert flattened_key is not None
+
+        if is_list(obj):
+            files: list[tuple[str, FileTypes]] = []
+            for entry in obj:
+                assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
+                files.append((flattened_key + "[]", cast(FileTypes, entry)))
+            return files
+
+        assert_is_file_content(obj, key=flattened_key)
+        return [(flattened_key, cast(FileTypes, obj))]
+
+    index += 1
+    if is_dict(obj):
+        try:
+            # We are at the last entry in the path so we must remove the field
+            if (len(path)) == index:
+                item = obj.pop(key)
+            else:
+                item = obj[key]
+        except KeyError:
+            # Key was not present in the dictionary, this is not indicative of an error
+            # as the given path may not point to a required field. We also do not want
+            # to enforce required fields as the API may differ from the spec in some cases.
+            return []
+        if flattened_key is None:
+            flattened_key = key
+        else:
+            flattened_key += f"[{key}]"
+        return _extract_items(
+            item,
+            path,
+            index=index,
+            flattened_key=flattened_key,
+        )
+    elif is_list(obj):
+        if key != "<array>":
+            return []
+
+        return flatten(
+            [
+                _extract_items(
+                    item,
+                    path,
+                    index=index,
+                    flattened_key=flattened_key + "[]" if flattened_key is not None else "[]",
+                )
+                for item in obj
+            ]
+        )
+
+    # Something unexpected was passed, just ignore it.
+    return []
+
+
+def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]:
+    return not isinstance(obj, NotGiven)
+
+
+# Type safe methods for narrowing types with TypeVars.
+# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
+# however this cause Pyright to rightfully report errors. As we know we don't
+# care about the contained types we can safely use `object` in it's place.
+#
+# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
+# `is_*` is for when you're dealing with an unknown input
+# `is_*_t` is for when you're narrowing a known union type to a specific subset
+
+
+def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]:
+    return isinstance(obj, tuple)
+
+
+def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]:
+    return isinstance(obj, tuple)
+
+
+def is_sequence(obj: object) -> TypeGuard[Sequence[object]]:
+    return isinstance(obj, Sequence)
+
+
+def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]:
+    return isinstance(obj, Sequence)
+
+
+def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]:
+    return isinstance(obj, Mapping)
+
+
+def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]:
+    return isinstance(obj, Mapping)
+
+
+def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
+    return isinstance(obj, dict)
+
+
+def is_list(obj: object) -> TypeGuard[list[object]]:
+    return isinstance(obj, list)
+
+
+def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
+    return isinstance(obj, Iterable)
+
+
+def deepcopy_minimal(item: _T) -> _T:
+    """Minimal reimplementation of copy.deepcopy() that will only copy certain object types:
+
+    - mappings, e.g. `dict`
+    - list
+
+    This is done for performance reasons.
+    """
+    if is_mapping(item):
+        return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()})
+    if is_list(item):
+        return cast(_T, [deepcopy_minimal(entry) for entry in item])
+    return item
+
+
+# copied from https://github.com/Rapptz/RoboDanny
+def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
+    size = len(seq)
+    if size == 0:
+        return ""
+
+    if size == 1:
+        return seq[0]
+
+    if size == 2:
+        return f"{seq[0]} {final} {seq[1]}"
+
+    return delim.join(seq[:-1]) + f" {final} {seq[-1]}"
+
+
+def quote(string: str) -> str:
+    """Add single quotation marks around the given string. Does *not* do any escaping."""
+    return f"'{string}'"
+
+
+def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
+    """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function.
+
+    Useful for enforcing runtime validation of overloaded functions.
+
+    Example usage:
+    ```py
+    @overload
+    def foo(*, a: str) -> str: ...
+
+
+    @overload
+    def foo(*, b: bool) -> str: ...
+
+
+    # This enforces the same constraints that a static type checker would
+    # i.e. that either a or b must be passed to the function
+    @required_args(["a"], ["b"])
+    def foo(*, a: str | None = None, b: bool | None = None) -> str: ...
+    ```
+    """
+
+    def inner(func: CallableT) -> CallableT:
+        params = inspect.signature(func).parameters
+        positional = [
+            name
+            for name, param in params.items()
+            if param.kind
+            in {
+                param.POSITIONAL_ONLY,
+                param.POSITIONAL_OR_KEYWORD,
+            }
+        ]
+
+        @functools.wraps(func)
+        def wrapper(*args: object, **kwargs: object) -> object:
+            given_params: set[str] = set()
+            for i, _ in enumerate(args):
+                try:
+                    given_params.add(positional[i])
+                except IndexError:
+                    raise TypeError(
+                        f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given"
+                    ) from None
+
+            for key in kwargs.keys():
+                given_params.add(key)
+
+            for variant in variants:
+                matches = all((param in given_params for param in variant))
+                if matches:
+                    break
+            else:  # no break
+                if len(variants) > 1:
+                    variations = human_join(
+                        ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants]
+                    )
+                    msg = f"Missing required arguments; Expected either {variations} arguments to be given"
+                else:
+                    assert len(variants) > 0
+
+                    # TODO: this error message is not deterministic
+                    missing = list(set(variants[0]) - given_params)
+                    if len(missing) > 1:
+                        msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}"
+                    else:
+                        msg = f"Missing required argument: {quote(missing[0])}"
+                raise TypeError(msg)
+            return func(*args, **kwargs)
+
+        return wrapper  # type: ignore
+
+    return inner
+
+
+_K = TypeVar("_K")
+_V = TypeVar("_V")
+
+
+@overload
+def strip_not_given(obj: None) -> None: ...
+
+
+@overload
+def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ...
+
+
+@overload
+def strip_not_given(obj: object) -> object: ...
+
+
+def strip_not_given(obj: object | None) -> object:
+    """Remove all top-level keys where their values are instances of `NotGiven`"""
+    if obj is None:
+        return None
+
+    if not is_mapping(obj):
+        return obj
+
+    return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)}
+
+
+def coerce_integer(val: str) -> int:
+    return int(val, base=10)
+
+
+def coerce_float(val: str) -> float:
+    return float(val)
+
+
+def coerce_boolean(val: str) -> bool:
+    return val == "true" or val == "1" or val == "on"
+
+
+def maybe_coerce_integer(val: str | None) -> int | None:
+    if val is None:
+        return None
+    return coerce_integer(val)
+
+
+def maybe_coerce_float(val: str | None) -> float | None:
+    if val is None:
+        return None
+    return coerce_float(val)
+
+
+def maybe_coerce_boolean(val: str | None) -> bool | None:
+    if val is None:
+        return None
+    return coerce_boolean(val)
+
+
+def removeprefix(string: str, prefix: str) -> str:
+    """Remove a prefix from a string.
+
+    Backport of `str.removeprefix` for Python < 3.9
+    """
+    if string.startswith(prefix):
+        return string[len(prefix) :]
+    return string
+
+
+def removesuffix(string: str, suffix: str) -> str:
+    """Remove a suffix from a string.
+
+    Backport of `str.removesuffix` for Python < 3.9
+    """
+    if string.endswith(suffix):
+        return string[: -len(suffix)]
+    return string
+
+
+def file_from_path(path: str) -> FileTypes:
+    contents = Path(path).read_bytes()
+    file_name = os.path.basename(path)
+    return (file_name, contents)
+
+
+def get_required_header(headers: HeadersLike, header: str) -> str:
+    lower_header = header.lower()
+    if is_mapping_t(headers):
+        # mypy doesn't understand the type narrowing here
+        for k, v in headers.items():  # type: ignore
+            if k.lower() == lower_header and isinstance(v, str):
+                return v
+
+    # to deal with the case where the header looks like Stainless-Event-Id
+    intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize())
+
+    for normalized_header in [header, lower_header, header.upper(), intercaps_header]:
+        value = headers.get(normalized_header)
+        if value:
+            return value
+
+    raise ValueError(f"Could not find {header} header")
+
+
+def get_async_library() -> str:
+    try:
+        return sniffio.current_async_library()
+    except Exception:
+        return "false"
+
+
+def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
+    """A version of functools.lru_cache that retains the type signature
+    for the wrapped function arguments.
+    """
+    wrapper = functools.lru_cache(  # noqa: TID251
+        maxsize=maxsize,
+    )
+    return cast(Any, wrapper)  # type: ignore[no-any-return]
+
+
+def json_safe(data: object) -> object:
+    """Translates a mapping / sequence recursively in the same fashion
+    as `pydantic` v2's `model_dump(mode="json")`.
+    """
+    if is_mapping(data):
+        return {json_safe(key): json_safe(value) for key, value in data.items()}
+
+    if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
+        return [json_safe(item) for item in data]
+
+    if isinstance(data, (datetime, date)):
+        return data.isoformat()
+
+    return data
diff --git a/src/gitpod/_version.py b/src/gitpod/_version.py
new file mode 100644
index 0000000..1bd510f
--- /dev/null
+++ b/src/gitpod/_version.py
@@ -0,0 +1,4 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+__title__ = "gitpod"
+__version__ = "0.3.0"  # x-release-please-version
diff --git a/src/gitpod/lib/.keep b/src/gitpod/lib/.keep
new file mode 100644
index 0000000..5e2c99f
--- /dev/null
+++ b/src/gitpod/lib/.keep
@@ -0,0 +1,4 @@
+File generated from our OpenAPI spec by Stainless.
+
+This directory can be used to store custom files to expand the SDK.
+It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
\ No newline at end of file
diff --git a/src/gitpod/pagination.py b/src/gitpod/pagination.py
new file mode 100644
index 0000000..647ccd9
--- /dev/null
+++ b/src/gitpod/pagination.py
@@ -0,0 +1,1127 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Generic, TypeVar, Optional
+from typing_extensions import override
+
+from pydantic import Field as FieldInfo
+
+from ._models import BaseModel
+from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
+
+__all__ = [
+    "DomainVerificationsPagePagination",
+    "SyncDomainVerificationsPage",
+    "AsyncDomainVerificationsPage",
+    "EditorsPagePagination",
+    "SyncEditorsPage",
+    "AsyncEditorsPage",
+    "EntriesPagePagination",
+    "SyncEntriesPage",
+    "AsyncEntriesPage",
+    "EnvironmentClassesPagePagination",
+    "SyncEnvironmentClassesPage",
+    "AsyncEnvironmentClassesPage",
+    "EnvironmentsPagePagination",
+    "SyncEnvironmentsPage",
+    "AsyncEnvironmentsPage",
+    "GatewaysPagePagination",
+    "SyncGatewaysPage",
+    "AsyncGatewaysPage",
+    "GroupsPagePagination",
+    "SyncGroupsPage",
+    "AsyncGroupsPage",
+    "IntegrationsPagePagination",
+    "SyncIntegrationsPage",
+    "AsyncIntegrationsPage",
+    "LoginProvidersPagePagination",
+    "SyncLoginProvidersPage",
+    "AsyncLoginProvidersPage",
+    "MembersPagePagination",
+    "SyncMembersPage",
+    "AsyncMembersPage",
+    "PersonalAccessTokensPagePagination",
+    "SyncPersonalAccessTokensPage",
+    "AsyncPersonalAccessTokensPage",
+    "PoliciesPagePagination",
+    "SyncPoliciesPage",
+    "AsyncPoliciesPage",
+    "ProjectsPagePagination",
+    "SyncProjectsPage",
+    "AsyncProjectsPage",
+    "RecordsPagePagination",
+    "SyncRecordsPage",
+    "AsyncRecordsPage",
+    "RunnersPagePagination",
+    "SyncRunnersPage",
+    "AsyncRunnersPage",
+    "SecretsPagePagination",
+    "SyncSecretsPage",
+    "AsyncSecretsPage",
+    "ServicesPagePagination",
+    "SyncServicesPage",
+    "AsyncServicesPage",
+    "SSOConfigurationsPagePagination",
+    "SyncSSOConfigurationsPage",
+    "AsyncSSOConfigurationsPage",
+    "TaskExecutionsPagePagination",
+    "SyncTaskExecutionsPage",
+    "AsyncTaskExecutionsPage",
+    "TasksPagePagination",
+    "SyncTasksPage",
+    "AsyncTasksPage",
+    "TokensPagePagination",
+    "SyncTokensPage",
+    "AsyncTokensPage",
+]
+
+_T = TypeVar("_T")
+
+
+class DomainVerificationsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncDomainVerificationsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    domain_verifications: List[_T] = FieldInfo(alias="domainVerifications")
+    pagination: Optional[DomainVerificationsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        domain_verifications = self.domain_verifications
+        if not domain_verifications:
+            return []
+        return domain_verifications
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncDomainVerificationsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    domain_verifications: List[_T] = FieldInfo(alias="domainVerifications")
+    pagination: Optional[DomainVerificationsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        domain_verifications = self.domain_verifications
+        if not domain_verifications:
+            return []
+        return domain_verifications
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class EditorsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncEditorsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    editors: List[_T]
+    pagination: Optional[EditorsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        editors = self.editors
+        if not editors:
+            return []
+        return editors
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncEditorsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    editors: List[_T]
+    pagination: Optional[EditorsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        editors = self.editors
+        if not editors:
+            return []
+        return editors
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class EntriesPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncEntriesPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    entries: List[_T]
+    pagination: Optional[EntriesPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        entries = self.entries
+        if not entries:
+            return []
+        return entries
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncEntriesPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    entries: List[_T]
+    pagination: Optional[EntriesPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        entries = self.entries
+        if not entries:
+            return []
+        return entries
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class EnvironmentClassesPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncEnvironmentClassesPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    environment_classes: List[_T] = FieldInfo(alias="environmentClasses")
+    pagination: Optional[EnvironmentClassesPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        environment_classes = self.environment_classes
+        if not environment_classes:
+            return []
+        return environment_classes
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncEnvironmentClassesPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    environment_classes: List[_T] = FieldInfo(alias="environmentClasses")
+    pagination: Optional[EnvironmentClassesPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        environment_classes = self.environment_classes
+        if not environment_classes:
+            return []
+        return environment_classes
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class EnvironmentsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncEnvironmentsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    environments: List[_T]
+    pagination: Optional[EnvironmentsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        environments = self.environments
+        if not environments:
+            return []
+        return environments
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncEnvironmentsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    environments: List[_T]
+    pagination: Optional[EnvironmentsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        environments = self.environments
+        if not environments:
+            return []
+        return environments
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class GatewaysPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncGatewaysPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    gateways: List[_T]
+    pagination: Optional[GatewaysPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        gateways = self.gateways
+        if not gateways:
+            return []
+        return gateways
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncGatewaysPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    gateways: List[_T]
+    pagination: Optional[GatewaysPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        gateways = self.gateways
+        if not gateways:
+            return []
+        return gateways
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class GroupsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncGroupsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    groups: List[_T]
+    pagination: Optional[GroupsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        groups = self.groups
+        if not groups:
+            return []
+        return groups
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncGroupsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    groups: List[_T]
+    pagination: Optional[GroupsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        groups = self.groups
+        if not groups:
+            return []
+        return groups
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class IntegrationsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncIntegrationsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    integrations: List[_T]
+    pagination: Optional[IntegrationsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        integrations = self.integrations
+        if not integrations:
+            return []
+        return integrations
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncIntegrationsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    integrations: List[_T]
+    pagination: Optional[IntegrationsPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        integrations = self.integrations
+        if not integrations:
+            return []
+        return integrations
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class LoginProvidersPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncLoginProvidersPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    login_providers: List[_T] = FieldInfo(alias="loginProviders")
+    pagination: Optional[LoginProvidersPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        login_providers = self.login_providers
+        if not login_providers:
+            return []
+        return login_providers
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncLoginProvidersPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    login_providers: List[_T] = FieldInfo(alias="loginProviders")
+    pagination: Optional[LoginProvidersPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        login_providers = self.login_providers
+        if not login_providers:
+            return []
+        return login_providers
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class MembersPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncMembersPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    members: List[_T]
+    pagination: Optional[MembersPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        members = self.members
+        if not members:
+            return []
+        return members
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncMembersPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    members: List[_T]
+    pagination: Optional[MembersPagePagination] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        members = self.members
+        if not members:
+            return []
+        return members
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class PersonalAccessTokensPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncPersonalAccessTokensPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[PersonalAccessTokensPagePagination] = None
+    personal_access_tokens: List[_T] = FieldInfo(alias="personalAccessTokens")
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        personal_access_tokens = self.personal_access_tokens
+        if not personal_access_tokens:
+            return []
+        return personal_access_tokens
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncPersonalAccessTokensPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[PersonalAccessTokensPagePagination] = None
+    personal_access_tokens: List[_T] = FieldInfo(alias="personalAccessTokens")
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        personal_access_tokens = self.personal_access_tokens
+        if not personal_access_tokens:
+            return []
+        return personal_access_tokens
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class PoliciesPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncPoliciesPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[PoliciesPagePagination] = None
+    policies: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        policies = self.policies
+        if not policies:
+            return []
+        return policies
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncPoliciesPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[PoliciesPagePagination] = None
+    policies: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        policies = self.policies
+        if not policies:
+            return []
+        return policies
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class ProjectsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncProjectsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[ProjectsPagePagination] = None
+    projects: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        projects = self.projects
+        if not projects:
+            return []
+        return projects
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncProjectsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[ProjectsPagePagination] = None
+    projects: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        projects = self.projects
+        if not projects:
+            return []
+        return projects
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class RecordsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncRecordsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[RecordsPagePagination] = None
+    records: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        records = self.records
+        if not records:
+            return []
+        return records
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncRecordsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[RecordsPagePagination] = None
+    records: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        records = self.records
+        if not records:
+            return []
+        return records
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class RunnersPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncRunnersPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[RunnersPagePagination] = None
+    runners: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        runners = self.runners
+        if not runners:
+            return []
+        return runners
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncRunnersPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[RunnersPagePagination] = None
+    runners: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        runners = self.runners
+        if not runners:
+            return []
+        return runners
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class SecretsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncSecretsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[SecretsPagePagination] = None
+    secrets: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        secrets = self.secrets
+        if not secrets:
+            return []
+        return secrets
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncSecretsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[SecretsPagePagination] = None
+    secrets: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        secrets = self.secrets
+        if not secrets:
+            return []
+        return secrets
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class ServicesPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncServicesPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[ServicesPagePagination] = None
+    services: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        services = self.services
+        if not services:
+            return []
+        return services
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncServicesPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[ServicesPagePagination] = None
+    services: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        services = self.services
+        if not services:
+            return []
+        return services
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class SSOConfigurationsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncSSOConfigurationsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[SSOConfigurationsPagePagination] = None
+    sso_configurations: List[_T] = FieldInfo(alias="ssoConfigurations")
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        sso_configurations = self.sso_configurations
+        if not sso_configurations:
+            return []
+        return sso_configurations
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncSSOConfigurationsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[SSOConfigurationsPagePagination] = None
+    sso_configurations: List[_T] = FieldInfo(alias="ssoConfigurations")
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        sso_configurations = self.sso_configurations
+        if not sso_configurations:
+            return []
+        return sso_configurations
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class TaskExecutionsPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncTaskExecutionsPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[TaskExecutionsPagePagination] = None
+    task_executions: List[_T] = FieldInfo(alias="taskExecutions")
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        task_executions = self.task_executions
+        if not task_executions:
+            return []
+        return task_executions
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncTaskExecutionsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[TaskExecutionsPagePagination] = None
+    task_executions: List[_T] = FieldInfo(alias="taskExecutions")
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        task_executions = self.task_executions
+        if not task_executions:
+            return []
+        return task_executions
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class TasksPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncTasksPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[TasksPagePagination] = None
+    tasks: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        tasks = self.tasks
+        if not tasks:
+            return []
+        return tasks
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncTasksPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[TasksPagePagination] = None
+    tasks: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        tasks = self.tasks
+        if not tasks:
+            return []
+        return tasks
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class TokensPagePagination(BaseModel):
+    next_token: Optional[str] = FieldInfo(alias="nextToken", default=None)
+
+
+class SyncTokensPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[TokensPagePagination] = None
+    tokens: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        tokens = self.tokens
+        if not tokens:
+            return []
+        return tokens
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
+
+
+class AsyncTokensPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    pagination: Optional[TokensPagePagination] = None
+    tokens: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        tokens = self.tokens
+        if not tokens:
+            return []
+        return tokens
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        next_token = None
+        if self.pagination is not None:
+            if self.pagination.next_token is not None:
+                next_token = self.pagination.next_token
+        if not next_token:
+            return None
+
+        return PageInfo(params={"token": next_token})
diff --git a/src/gitpod/py.typed b/src/gitpod/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/src/gitpod/resources/__init__.py b/src/gitpod/resources/__init__.py
new file mode 100644
index 0000000..fe63612
--- /dev/null
+++ b/src/gitpod/resources/__init__.py
@@ -0,0 +1,187 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .usage import (
+    UsageResource,
+    AsyncUsageResource,
+    UsageResourceWithRawResponse,
+    AsyncUsageResourceWithRawResponse,
+    UsageResourceWithStreamingResponse,
+    AsyncUsageResourceWithStreamingResponse,
+)
+from .users import (
+    UsersResource,
+    AsyncUsersResource,
+    UsersResourceWithRawResponse,
+    AsyncUsersResourceWithRawResponse,
+    UsersResourceWithStreamingResponse,
+    AsyncUsersResourceWithStreamingResponse,
+)
+from .events import (
+    EventsResource,
+    AsyncEventsResource,
+    EventsResourceWithRawResponse,
+    AsyncEventsResourceWithRawResponse,
+    EventsResourceWithStreamingResponse,
+    AsyncEventsResourceWithStreamingResponse,
+)
+from .groups import (
+    GroupsResource,
+    AsyncGroupsResource,
+    GroupsResourceWithRawResponse,
+    AsyncGroupsResourceWithRawResponse,
+    GroupsResourceWithStreamingResponse,
+    AsyncGroupsResourceWithStreamingResponse,
+)
+from .editors import (
+    EditorsResource,
+    AsyncEditorsResource,
+    EditorsResourceWithRawResponse,
+    AsyncEditorsResourceWithRawResponse,
+    EditorsResourceWithStreamingResponse,
+    AsyncEditorsResourceWithStreamingResponse,
+)
+from .runners import (
+    RunnersResource,
+    AsyncRunnersResource,
+    RunnersResourceWithRawResponse,
+    AsyncRunnersResourceWithRawResponse,
+    RunnersResourceWithStreamingResponse,
+    AsyncRunnersResourceWithStreamingResponse,
+)
+from .secrets import (
+    SecretsResource,
+    AsyncSecretsResource,
+    SecretsResourceWithRawResponse,
+    AsyncSecretsResourceWithRawResponse,
+    SecretsResourceWithStreamingResponse,
+    AsyncSecretsResourceWithStreamingResponse,
+)
+from .accounts import (
+    AccountsResource,
+    AsyncAccountsResource,
+    AccountsResourceWithRawResponse,
+    AsyncAccountsResourceWithRawResponse,
+    AccountsResourceWithStreamingResponse,
+    AsyncAccountsResourceWithStreamingResponse,
+)
+from .gateways import (
+    GatewaysResource,
+    AsyncGatewaysResource,
+    GatewaysResourceWithRawResponse,
+    AsyncGatewaysResourceWithRawResponse,
+    GatewaysResourceWithStreamingResponse,
+    AsyncGatewaysResourceWithStreamingResponse,
+)
+from .identity import (
+    IdentityResource,
+    AsyncIdentityResource,
+    IdentityResourceWithRawResponse,
+    AsyncIdentityResourceWithRawResponse,
+    IdentityResourceWithStreamingResponse,
+    AsyncIdentityResourceWithStreamingResponse,
+)
+from .projects import (
+    ProjectsResource,
+    AsyncProjectsResource,
+    ProjectsResourceWithRawResponse,
+    AsyncProjectsResourceWithRawResponse,
+    ProjectsResourceWithStreamingResponse,
+    AsyncProjectsResourceWithStreamingResponse,
+)
+from .environments import (
+    EnvironmentsResource,
+    AsyncEnvironmentsResource,
+    EnvironmentsResourceWithRawResponse,
+    AsyncEnvironmentsResourceWithRawResponse,
+    EnvironmentsResourceWithStreamingResponse,
+    AsyncEnvironmentsResourceWithStreamingResponse,
+)
+from .organizations import (
+    OrganizationsResource,
+    AsyncOrganizationsResource,
+    OrganizationsResourceWithRawResponse,
+    AsyncOrganizationsResourceWithRawResponse,
+    OrganizationsResourceWithStreamingResponse,
+    AsyncOrganizationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "AccountsResource",
+    "AsyncAccountsResource",
+    "AccountsResourceWithRawResponse",
+    "AsyncAccountsResourceWithRawResponse",
+    "AccountsResourceWithStreamingResponse",
+    "AsyncAccountsResourceWithStreamingResponse",
+    "EditorsResource",
+    "AsyncEditorsResource",
+    "EditorsResourceWithRawResponse",
+    "AsyncEditorsResourceWithRawResponse",
+    "EditorsResourceWithStreamingResponse",
+    "AsyncEditorsResourceWithStreamingResponse",
+    "EnvironmentsResource",
+    "AsyncEnvironmentsResource",
+    "EnvironmentsResourceWithRawResponse",
+    "AsyncEnvironmentsResourceWithRawResponse",
+    "EnvironmentsResourceWithStreamingResponse",
+    "AsyncEnvironmentsResourceWithStreamingResponse",
+    "EventsResource",
+    "AsyncEventsResource",
+    "EventsResourceWithRawResponse",
+    "AsyncEventsResourceWithRawResponse",
+    "EventsResourceWithStreamingResponse",
+    "AsyncEventsResourceWithStreamingResponse",
+    "GatewaysResource",
+    "AsyncGatewaysResource",
+    "GatewaysResourceWithRawResponse",
+    "AsyncGatewaysResourceWithRawResponse",
+    "GatewaysResourceWithStreamingResponse",
+    "AsyncGatewaysResourceWithStreamingResponse",
+    "GroupsResource",
+    "AsyncGroupsResource",
+    "GroupsResourceWithRawResponse",
+    "AsyncGroupsResourceWithRawResponse",
+    "GroupsResourceWithStreamingResponse",
+    "AsyncGroupsResourceWithStreamingResponse",
+    "IdentityResource",
+    "AsyncIdentityResource",
+    "IdentityResourceWithRawResponse",
+    "AsyncIdentityResourceWithRawResponse",
+    "IdentityResourceWithStreamingResponse",
+    "AsyncIdentityResourceWithStreamingResponse",
+    "OrganizationsResource",
+    "AsyncOrganizationsResource",
+    "OrganizationsResourceWithRawResponse",
+    "AsyncOrganizationsResourceWithRawResponse",
+    "OrganizationsResourceWithStreamingResponse",
+    "AsyncOrganizationsResourceWithStreamingResponse",
+    "ProjectsResource",
+    "AsyncProjectsResource",
+    "ProjectsResourceWithRawResponse",
+    "AsyncProjectsResourceWithRawResponse",
+    "ProjectsResourceWithStreamingResponse",
+    "AsyncProjectsResourceWithStreamingResponse",
+    "RunnersResource",
+    "AsyncRunnersResource",
+    "RunnersResourceWithRawResponse",
+    "AsyncRunnersResourceWithRawResponse",
+    "RunnersResourceWithStreamingResponse",
+    "AsyncRunnersResourceWithStreamingResponse",
+    "SecretsResource",
+    "AsyncSecretsResource",
+    "SecretsResourceWithRawResponse",
+    "AsyncSecretsResourceWithRawResponse",
+    "SecretsResourceWithStreamingResponse",
+    "AsyncSecretsResourceWithStreamingResponse",
+    "UsageResource",
+    "AsyncUsageResource",
+    "UsageResourceWithRawResponse",
+    "AsyncUsageResourceWithRawResponse",
+    "UsageResourceWithStreamingResponse",
+    "AsyncUsageResourceWithStreamingResponse",
+    "UsersResource",
+    "AsyncUsersResource",
+    "UsersResourceWithRawResponse",
+    "AsyncUsersResourceWithRawResponse",
+    "UsersResourceWithStreamingResponse",
+    "AsyncUsersResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/accounts.py b/src/gitpod/resources/accounts.py
new file mode 100644
index 0000000..e756632
--- /dev/null
+++ b/src/gitpod/resources/accounts.py
@@ -0,0 +1,787 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..types import (
+    account_delete_params,
+    account_retrieve_params,
+    account_get_sso_login_url_params,
+    account_list_login_providers_params,
+    account_list_joinable_organizations_params,
+)
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncLoginProvidersPage, AsyncLoginProvidersPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.login_provider import LoginProvider
+from ..types.account_retrieve_response import AccountRetrieveResponse
+from ..types.account_get_sso_login_url_response import AccountGetSSOLoginURLResponse
+from ..types.account_list_joinable_organizations_response import AccountListJoinableOrganizationsResponse
+
+__all__ = ["AccountsResource", "AsyncAccountsResource"]
+
+
+class AccountsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AccountsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AccountsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AccountsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AccountsResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccountRetrieveResponse:
+        """
+        Gets information about the currently authenticated account.
+
+        Use this method to:
+
+        - Retrieve account profile information
+        - Check organization memberships
+        - View account settings
+        - Get joinable organizations
+
+        ### Examples
+
+        - Get account details:
+
+          Retrieves information about the authenticated account.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.AccountService/GetAccount",
+            body=maybe_transform({"empty": empty}, account_retrieve_params.AccountRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccountRetrieveResponse,
+        )
+
+    def delete(
+        self,
+        *,
+        account_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes an account permanently.
+
+        Use this method to:
+
+        - Remove unused accounts
+        - Clean up test accounts
+        - Complete account deletion requests
+
+        The account must not be an active member of any organization.
+
+        ### Examples
+
+        - Delete account:
+
+          Permanently removes an account.
+
+          ```yaml
+          accountId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.AccountService/DeleteAccount",
+            body=maybe_transform({"account_id": account_id}, account_delete_params.AccountDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def get_sso_login_url(
+        self,
+        *,
+        email: str,
+        return_to: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccountGetSSOLoginURLResponse:
+        """
+        Gets the SSO login URL for a specific email domain.
+
+        Use this method to:
+
+        - Initiate SSO authentication
+        - Get organization-specific login URLs
+        - Handle SSO redirects
+
+        ### Examples
+
+        - Get login URL:
+
+          Retrieves SSO URL for email domain.
+
+          ```yaml
+          email: "user@company.com"
+          ```
+
+        - Get URL with return path:
+
+          Gets SSO URL with specific return location.
+
+          ```yaml
+          email: "user@company.com"
+          returnTo: "https://gitpod.io/workspaces"
+          ```
+
+        Args:
+          email: email is the email the user wants to login with
+
+          return_to: return_to is the URL the user will be redirected to after login
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.AccountService/GetSSOLoginURL",
+            body=maybe_transform(
+                {
+                    "email": email,
+                    "return_to": return_to,
+                },
+                account_get_sso_login_url_params.AccountGetSSOLoginURLParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccountGetSSOLoginURLResponse,
+        )
+
+    def list_joinable_organizations(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccountListJoinableOrganizationsResponse:
+        """
+        Lists organizations that the currently authenticated account can join.
+
+        Use this method to:
+
+        - Discover organizations associated with the account's email domain.
+        - Allow users to join existing organizations.
+        - Display potential organizations during onboarding.
+
+        ### Examples
+
+        - List joinable organizations:
+
+          Retrieves a list of organizations the account can join.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.AccountService/ListJoinableOrganizations",
+            body=maybe_transform(
+                {"empty": empty}, account_list_joinable_organizations_params.AccountListJoinableOrganizationsParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    account_list_joinable_organizations_params.AccountListJoinableOrganizationsParams,
+                ),
+            ),
+            cast_to=AccountListJoinableOrganizationsResponse,
+        )
+
+    def list_login_providers(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: account_list_login_providers_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: account_list_login_providers_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncLoginProvidersPage[LoginProvider]:
+        """
+        Lists available login providers with optional filtering.
+
+        Use this method to:
+
+        - View supported authentication methods
+        - Get provider-specific login URLs
+        - Filter providers by invite
+
+        ### Examples
+
+        - List all providers:
+
+          Shows all available login providers.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - List for specific invite:
+
+          Shows providers available for an invite.
+
+          ```yaml
+          filter:
+            inviteId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing login methods
+
+          pagination: pagination contains the pagination options for listing login methods
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.AccountService/ListLoginProviders",
+            page=SyncLoginProvidersPage[LoginProvider],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                account_list_login_providers_params.AccountListLoginProvidersParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    account_list_login_providers_params.AccountListLoginProvidersParams,
+                ),
+            ),
+            model=LoginProvider,
+            method="post",
+        )
+
+
+class AsyncAccountsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncAccountsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncAccountsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncAccountsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncAccountsResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccountRetrieveResponse:
+        """
+        Gets information about the currently authenticated account.
+
+        Use this method to:
+
+        - Retrieve account profile information
+        - Check organization memberships
+        - View account settings
+        - Get joinable organizations
+
+        ### Examples
+
+        - Get account details:
+
+          Retrieves information about the authenticated account.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.AccountService/GetAccount",
+            body=await async_maybe_transform({"empty": empty}, account_retrieve_params.AccountRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccountRetrieveResponse,
+        )
+
+    async def delete(
+        self,
+        *,
+        account_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes an account permanently.
+
+        Use this method to:
+
+        - Remove unused accounts
+        - Clean up test accounts
+        - Complete account deletion requests
+
+        The account must not be an active member of any organization.
+
+        ### Examples
+
+        - Delete account:
+
+          Permanently removes an account.
+
+          ```yaml
+          accountId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.AccountService/DeleteAccount",
+            body=await async_maybe_transform({"account_id": account_id}, account_delete_params.AccountDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def get_sso_login_url(
+        self,
+        *,
+        email: str,
+        return_to: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccountGetSSOLoginURLResponse:
+        """
+        Gets the SSO login URL for a specific email domain.
+
+        Use this method to:
+
+        - Initiate SSO authentication
+        - Get organization-specific login URLs
+        - Handle SSO redirects
+
+        ### Examples
+
+        - Get login URL:
+
+          Retrieves SSO URL for email domain.
+
+          ```yaml
+          email: "user@company.com"
+          ```
+
+        - Get URL with return path:
+
+          Gets SSO URL with specific return location.
+
+          ```yaml
+          email: "user@company.com"
+          returnTo: "https://gitpod.io/workspaces"
+          ```
+
+        Args:
+          email: email is the email the user wants to login with
+
+          return_to: return_to is the URL the user will be redirected to after login
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.AccountService/GetSSOLoginURL",
+            body=await async_maybe_transform(
+                {
+                    "email": email,
+                    "return_to": return_to,
+                },
+                account_get_sso_login_url_params.AccountGetSSOLoginURLParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccountGetSSOLoginURLResponse,
+        )
+
+    async def list_joinable_organizations(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccountListJoinableOrganizationsResponse:
+        """
+        Lists organizations that the currently authenticated account can join.
+
+        Use this method to:
+
+        - Discover organizations associated with the account's email domain.
+        - Allow users to join existing organizations.
+        - Display potential organizations during onboarding.
+
+        ### Examples
+
+        - List joinable organizations:
+
+          Retrieves a list of organizations the account can join.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.AccountService/ListJoinableOrganizations",
+            body=await async_maybe_transform(
+                {"empty": empty}, account_list_joinable_organizations_params.AccountListJoinableOrganizationsParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    account_list_joinable_organizations_params.AccountListJoinableOrganizationsParams,
+                ),
+            ),
+            cast_to=AccountListJoinableOrganizationsResponse,
+        )
+
+    def list_login_providers(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: account_list_login_providers_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: account_list_login_providers_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[LoginProvider, AsyncLoginProvidersPage[LoginProvider]]:
+        """
+        Lists available login providers with optional filtering.
+
+        Use this method to:
+
+        - View supported authentication methods
+        - Get provider-specific login URLs
+        - Filter providers by invite
+
+        ### Examples
+
+        - List all providers:
+
+          Shows all available login providers.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - List for specific invite:
+
+          Shows providers available for an invite.
+
+          ```yaml
+          filter:
+            inviteId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing login methods
+
+          pagination: pagination contains the pagination options for listing login methods
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.AccountService/ListLoginProviders",
+            page=AsyncLoginProvidersPage[LoginProvider],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                account_list_login_providers_params.AccountListLoginProvidersParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    account_list_login_providers_params.AccountListLoginProvidersParams,
+                ),
+            ),
+            model=LoginProvider,
+            method="post",
+        )
+
+
+class AccountsResourceWithRawResponse:
+    def __init__(self, accounts: AccountsResource) -> None:
+        self._accounts = accounts
+
+        self.retrieve = to_raw_response_wrapper(
+            accounts.retrieve,
+        )
+        self.delete = to_raw_response_wrapper(
+            accounts.delete,
+        )
+        self.get_sso_login_url = to_raw_response_wrapper(
+            accounts.get_sso_login_url,
+        )
+        self.list_joinable_organizations = to_raw_response_wrapper(
+            accounts.list_joinable_organizations,
+        )
+        self.list_login_providers = to_raw_response_wrapper(
+            accounts.list_login_providers,
+        )
+
+
+class AsyncAccountsResourceWithRawResponse:
+    def __init__(self, accounts: AsyncAccountsResource) -> None:
+        self._accounts = accounts
+
+        self.retrieve = async_to_raw_response_wrapper(
+            accounts.retrieve,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            accounts.delete,
+        )
+        self.get_sso_login_url = async_to_raw_response_wrapper(
+            accounts.get_sso_login_url,
+        )
+        self.list_joinable_organizations = async_to_raw_response_wrapper(
+            accounts.list_joinable_organizations,
+        )
+        self.list_login_providers = async_to_raw_response_wrapper(
+            accounts.list_login_providers,
+        )
+
+
+class AccountsResourceWithStreamingResponse:
+    def __init__(self, accounts: AccountsResource) -> None:
+        self._accounts = accounts
+
+        self.retrieve = to_streamed_response_wrapper(
+            accounts.retrieve,
+        )
+        self.delete = to_streamed_response_wrapper(
+            accounts.delete,
+        )
+        self.get_sso_login_url = to_streamed_response_wrapper(
+            accounts.get_sso_login_url,
+        )
+        self.list_joinable_organizations = to_streamed_response_wrapper(
+            accounts.list_joinable_organizations,
+        )
+        self.list_login_providers = to_streamed_response_wrapper(
+            accounts.list_login_providers,
+        )
+
+
+class AsyncAccountsResourceWithStreamingResponse:
+    def __init__(self, accounts: AsyncAccountsResource) -> None:
+        self._accounts = accounts
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            accounts.retrieve,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            accounts.delete,
+        )
+        self.get_sso_login_url = async_to_streamed_response_wrapper(
+            accounts.get_sso_login_url,
+        )
+        self.list_joinable_organizations = async_to_streamed_response_wrapper(
+            accounts.list_joinable_organizations,
+        )
+        self.list_login_providers = async_to_streamed_response_wrapper(
+            accounts.list_login_providers,
+        )
diff --git a/src/gitpod/resources/editors.py b/src/gitpod/resources/editors.py
new file mode 100644
index 0000000..3c3075f
--- /dev/null
+++ b/src/gitpod/resources/editors.py
@@ -0,0 +1,532 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..types import editor_list_params, editor_retrieve_params, editor_resolve_url_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncEditorsPage, AsyncEditorsPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.editor import Editor
+from ..types.editor_retrieve_response import EditorRetrieveResponse
+from ..types.editor_resolve_url_response import EditorResolveURLResponse
+
+__all__ = ["EditorsResource", "AsyncEditorsResource"]
+
+
+class EditorsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> EditorsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return EditorsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> EditorsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return EditorsResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        *,
+        id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EditorRetrieveResponse:
+        """
+        Gets details about a specific editor.
+
+        Use this method to:
+
+        - View editor information
+        - Get editor configuration
+
+        ### Examples
+
+        - Get editor details:
+
+          Retrieves information about a specific editor.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          id: id is the ID of the editor to get
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EditorService/GetEditor",
+            body=maybe_transform({"id": id}, editor_retrieve_params.EditorRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EditorRetrieveResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: editor_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: editor_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncEditorsPage[Editor]:
+        """
+        Lists all available code editors, optionally filtered to those allowed in an
+        organization.
+
+        Use this method to:
+
+        - View supported editors
+        - Get editor capabilities
+        - Browse editor options
+        - Check editor availability
+
+        ### Examples
+
+        - List editors:
+
+          Shows all available editors with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - List editors available to the organization:
+
+          Shows all available editors that are allowed by the policies enforced in the
+          organization with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          filter:
+            allowedByPolicy: true
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing editors
+
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EditorService/ListEditors",
+            page=SyncEditorsPage[Editor],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                editor_list_params.EditorListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    editor_list_params.EditorListParams,
+                ),
+            ),
+            model=Editor,
+            method="post",
+        )
+
+    def resolve_url(
+        self,
+        *,
+        editor_id: str,
+        environment_id: str,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EditorResolveURLResponse:
+        """
+        Resolves the URL for accessing an editor in a specific environment.
+
+        Use this method to:
+
+        - Get editor access URLs
+        - Launch editors for environments
+        - Set up editor connections
+        - Configure editor access
+
+        ### Examples
+
+        - Resolve editor URL:
+
+          Gets the URL for accessing an editor in an environment.
+
+          ```yaml
+          editorId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          editor_id: editorId is the ID of the editor to resolve the URL for
+
+          environment_id: environmentId is the ID of the environment to resolve the URL for
+
+          organization_id: organizationId is the ID of the organization to resolve the URL for
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EditorService/ResolveEditorURL",
+            body=maybe_transform(
+                {
+                    "editor_id": editor_id,
+                    "environment_id": environment_id,
+                    "organization_id": organization_id,
+                },
+                editor_resolve_url_params.EditorResolveURLParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EditorResolveURLResponse,
+        )
+
+
+class AsyncEditorsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncEditorsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncEditorsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncEditorsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncEditorsResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        *,
+        id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EditorRetrieveResponse:
+        """
+        Gets details about a specific editor.
+
+        Use this method to:
+
+        - View editor information
+        - Get editor configuration
+
+        ### Examples
+
+        - Get editor details:
+
+          Retrieves information about a specific editor.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          id: id is the ID of the editor to get
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EditorService/GetEditor",
+            body=await async_maybe_transform({"id": id}, editor_retrieve_params.EditorRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EditorRetrieveResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: editor_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: editor_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Editor, AsyncEditorsPage[Editor]]:
+        """
+        Lists all available code editors, optionally filtered to those allowed in an
+        organization.
+
+        Use this method to:
+
+        - View supported editors
+        - Get editor capabilities
+        - Browse editor options
+        - Check editor availability
+
+        ### Examples
+
+        - List editors:
+
+          Shows all available editors with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - List editors available to the organization:
+
+          Shows all available editors that are allowed by the policies enforced in the
+          organization with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          filter:
+            allowedByPolicy: true
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing editors
+
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EditorService/ListEditors",
+            page=AsyncEditorsPage[Editor],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                editor_list_params.EditorListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    editor_list_params.EditorListParams,
+                ),
+            ),
+            model=Editor,
+            method="post",
+        )
+
+    async def resolve_url(
+        self,
+        *,
+        editor_id: str,
+        environment_id: str,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EditorResolveURLResponse:
+        """
+        Resolves the URL for accessing an editor in a specific environment.
+
+        Use this method to:
+
+        - Get editor access URLs
+        - Launch editors for environments
+        - Set up editor connections
+        - Configure editor access
+
+        ### Examples
+
+        - Resolve editor URL:
+
+          Gets the URL for accessing an editor in an environment.
+
+          ```yaml
+          editorId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          editor_id: editorId is the ID of the editor to resolve the URL for
+
+          environment_id: environmentId is the ID of the environment to resolve the URL for
+
+          organization_id: organizationId is the ID of the organization to resolve the URL for
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EditorService/ResolveEditorURL",
+            body=await async_maybe_transform(
+                {
+                    "editor_id": editor_id,
+                    "environment_id": environment_id,
+                    "organization_id": organization_id,
+                },
+                editor_resolve_url_params.EditorResolveURLParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EditorResolveURLResponse,
+        )
+
+
+class EditorsResourceWithRawResponse:
+    def __init__(self, editors: EditorsResource) -> None:
+        self._editors = editors
+
+        self.retrieve = to_raw_response_wrapper(
+            editors.retrieve,
+        )
+        self.list = to_raw_response_wrapper(
+            editors.list,
+        )
+        self.resolve_url = to_raw_response_wrapper(
+            editors.resolve_url,
+        )
+
+
+class AsyncEditorsResourceWithRawResponse:
+    def __init__(self, editors: AsyncEditorsResource) -> None:
+        self._editors = editors
+
+        self.retrieve = async_to_raw_response_wrapper(
+            editors.retrieve,
+        )
+        self.list = async_to_raw_response_wrapper(
+            editors.list,
+        )
+        self.resolve_url = async_to_raw_response_wrapper(
+            editors.resolve_url,
+        )
+
+
+class EditorsResourceWithStreamingResponse:
+    def __init__(self, editors: EditorsResource) -> None:
+        self._editors = editors
+
+        self.retrieve = to_streamed_response_wrapper(
+            editors.retrieve,
+        )
+        self.list = to_streamed_response_wrapper(
+            editors.list,
+        )
+        self.resolve_url = to_streamed_response_wrapper(
+            editors.resolve_url,
+        )
+
+
+class AsyncEditorsResourceWithStreamingResponse:
+    def __init__(self, editors: AsyncEditorsResource) -> None:
+        self._editors = editors
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            editors.retrieve,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            editors.list,
+        )
+        self.resolve_url = async_to_streamed_response_wrapper(
+            editors.resolve_url,
+        )
diff --git a/src/gitpod/resources/environments/__init__.py b/src/gitpod/resources/environments/__init__.py
new file mode 100644
index 0000000..854ce71
--- /dev/null
+++ b/src/gitpod/resources/environments/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .classes import (
+    ClassesResource,
+    AsyncClassesResource,
+    ClassesResourceWithRawResponse,
+    AsyncClassesResourceWithRawResponse,
+    ClassesResourceWithStreamingResponse,
+    AsyncClassesResourceWithStreamingResponse,
+)
+from .automations import (
+    AutomationsResource,
+    AsyncAutomationsResource,
+    AutomationsResourceWithRawResponse,
+    AsyncAutomationsResourceWithRawResponse,
+    AutomationsResourceWithStreamingResponse,
+    AsyncAutomationsResourceWithStreamingResponse,
+)
+from .environments import (
+    EnvironmentsResource,
+    AsyncEnvironmentsResource,
+    EnvironmentsResourceWithRawResponse,
+    AsyncEnvironmentsResourceWithRawResponse,
+    EnvironmentsResourceWithStreamingResponse,
+    AsyncEnvironmentsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "AutomationsResource",
+    "AsyncAutomationsResource",
+    "AutomationsResourceWithRawResponse",
+    "AsyncAutomationsResourceWithRawResponse",
+    "AutomationsResourceWithStreamingResponse",
+    "AsyncAutomationsResourceWithStreamingResponse",
+    "ClassesResource",
+    "AsyncClassesResource",
+    "ClassesResourceWithRawResponse",
+    "AsyncClassesResourceWithRawResponse",
+    "ClassesResourceWithStreamingResponse",
+    "AsyncClassesResourceWithStreamingResponse",
+    "EnvironmentsResource",
+    "AsyncEnvironmentsResource",
+    "EnvironmentsResourceWithRawResponse",
+    "AsyncEnvironmentsResourceWithRawResponse",
+    "EnvironmentsResourceWithStreamingResponse",
+    "AsyncEnvironmentsResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/environments/automations/__init__.py b/src/gitpod/resources/environments/automations/__init__.py
new file mode 100644
index 0000000..a9f609d
--- /dev/null
+++ b/src/gitpod/resources/environments/automations/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .tasks import (
+    TasksResource,
+    AsyncTasksResource,
+    TasksResourceWithRawResponse,
+    AsyncTasksResourceWithRawResponse,
+    TasksResourceWithStreamingResponse,
+    AsyncTasksResourceWithStreamingResponse,
+)
+from .services import (
+    ServicesResource,
+    AsyncServicesResource,
+    ServicesResourceWithRawResponse,
+    AsyncServicesResourceWithRawResponse,
+    ServicesResourceWithStreamingResponse,
+    AsyncServicesResourceWithStreamingResponse,
+)
+from .automations import (
+    AutomationsResource,
+    AsyncAutomationsResource,
+    AutomationsResourceWithRawResponse,
+    AsyncAutomationsResourceWithRawResponse,
+    AutomationsResourceWithStreamingResponse,
+    AsyncAutomationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "ServicesResource",
+    "AsyncServicesResource",
+    "ServicesResourceWithRawResponse",
+    "AsyncServicesResourceWithRawResponse",
+    "ServicesResourceWithStreamingResponse",
+    "AsyncServicesResourceWithStreamingResponse",
+    "TasksResource",
+    "AsyncTasksResource",
+    "TasksResourceWithRawResponse",
+    "AsyncTasksResourceWithRawResponse",
+    "TasksResourceWithStreamingResponse",
+    "AsyncTasksResourceWithStreamingResponse",
+    "AutomationsResource",
+    "AsyncAutomationsResource",
+    "AutomationsResourceWithRawResponse",
+    "AsyncAutomationsResourceWithRawResponse",
+    "AutomationsResourceWithStreamingResponse",
+    "AsyncAutomationsResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/environments/automations/automations.py b/src/gitpod/resources/environments/automations/automations.py
new file mode 100644
index 0000000..9eda852
--- /dev/null
+++ b/src/gitpod/resources/environments/automations/automations.py
@@ -0,0 +1,318 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .services import (
+    ServicesResource,
+    AsyncServicesResource,
+    ServicesResourceWithRawResponse,
+    AsyncServicesResourceWithRawResponse,
+    ServicesResourceWithStreamingResponse,
+    AsyncServicesResourceWithStreamingResponse,
+)
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from .tasks.tasks import (
+    TasksResource,
+    AsyncTasksResource,
+    TasksResourceWithRawResponse,
+    AsyncTasksResourceWithRawResponse,
+    TasksResourceWithStreamingResponse,
+    AsyncTasksResourceWithStreamingResponse,
+)
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.environments import automation_upsert_params
+from ....types.environments.automations_file_param import AutomationsFileParam
+from ....types.environments.automation_upsert_response import AutomationUpsertResponse
+
+__all__ = ["AutomationsResource", "AsyncAutomationsResource"]
+
+
+class AutomationsResource(SyncAPIResource):
+    @cached_property
+    def services(self) -> ServicesResource:
+        return ServicesResource(self._client)
+
+    @cached_property
+    def tasks(self) -> TasksResource:
+        return TasksResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AutomationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AutomationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AutomationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AutomationsResourceWithStreamingResponse(self)
+
+    def upsert(
+        self,
+        *,
+        automations_file: AutomationsFileParam | NotGiven = NOT_GIVEN,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AutomationUpsertResponse:
+        """
+        Upserts the automations file for the given environment.
+
+        Use this method to:
+
+        - Configure environment automations
+        - Update automation settings
+        - Manage automation files
+
+        ### Examples
+
+        - Update automations file:
+
+          Updates or creates the automations configuration.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          automationsFile:
+            services:
+              web-server:
+                name: "Web Server"
+                description: "Development web server"
+                commands:
+                  start: "npm run dev"
+                  ready: "curl -s http://localhost:3000"
+                triggeredBy:
+                  - postDevcontainerStart
+            tasks:
+              build:
+                name: "Build Project"
+                description: "Builds the project artifacts"
+                command: "npm run build"
+                triggeredBy:
+                  - postEnvironmentStart
+          ```
+
+        Args:
+          automations_file: WARN: Do not remove any field here, as it will break reading automation yaml
+              files. We error if there are any unknown fields in the yaml (to ensure the yaml
+              is correct), but would break if we removed any fields. This includes marking a
+              field as "reserved" in the proto file, this will also break reading the yaml.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/UpsertAutomationsFile",
+            body=maybe_transform(
+                {
+                    "automations_file": automations_file,
+                    "environment_id": environment_id,
+                },
+                automation_upsert_params.AutomationUpsertParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AutomationUpsertResponse,
+        )
+
+
+class AsyncAutomationsResource(AsyncAPIResource):
+    @cached_property
+    def services(self) -> AsyncServicesResource:
+        return AsyncServicesResource(self._client)
+
+    @cached_property
+    def tasks(self) -> AsyncTasksResource:
+        return AsyncTasksResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncAutomationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncAutomationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncAutomationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncAutomationsResourceWithStreamingResponse(self)
+
+    async def upsert(
+        self,
+        *,
+        automations_file: AutomationsFileParam | NotGiven = NOT_GIVEN,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AutomationUpsertResponse:
+        """
+        Upserts the automations file for the given environment.
+
+        Use this method to:
+
+        - Configure environment automations
+        - Update automation settings
+        - Manage automation files
+
+        ### Examples
+
+        - Update automations file:
+
+          Updates or creates the automations configuration.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          automationsFile:
+            services:
+              web-server:
+                name: "Web Server"
+                description: "Development web server"
+                commands:
+                  start: "npm run dev"
+                  ready: "curl -s http://localhost:3000"
+                triggeredBy:
+                  - postDevcontainerStart
+            tasks:
+              build:
+                name: "Build Project"
+                description: "Builds the project artifacts"
+                command: "npm run build"
+                triggeredBy:
+                  - postEnvironmentStart
+          ```
+
+        Args:
+          automations_file: WARN: Do not remove any field here, as it will break reading automation yaml
+              files. We error if there are any unknown fields in the yaml (to ensure the yaml
+              is correct), but would break if we removed any fields. This includes marking a
+              field as "reserved" in the proto file, this will also break reading the yaml.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/UpsertAutomationsFile",
+            body=await async_maybe_transform(
+                {
+                    "automations_file": automations_file,
+                    "environment_id": environment_id,
+                },
+                automation_upsert_params.AutomationUpsertParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AutomationUpsertResponse,
+        )
+
+
+class AutomationsResourceWithRawResponse:
+    def __init__(self, automations: AutomationsResource) -> None:
+        self._automations = automations
+
+        self.upsert = to_raw_response_wrapper(
+            automations.upsert,
+        )
+
+    @cached_property
+    def services(self) -> ServicesResourceWithRawResponse:
+        return ServicesResourceWithRawResponse(self._automations.services)
+
+    @cached_property
+    def tasks(self) -> TasksResourceWithRawResponse:
+        return TasksResourceWithRawResponse(self._automations.tasks)
+
+
+class AsyncAutomationsResourceWithRawResponse:
+    def __init__(self, automations: AsyncAutomationsResource) -> None:
+        self._automations = automations
+
+        self.upsert = async_to_raw_response_wrapper(
+            automations.upsert,
+        )
+
+    @cached_property
+    def services(self) -> AsyncServicesResourceWithRawResponse:
+        return AsyncServicesResourceWithRawResponse(self._automations.services)
+
+    @cached_property
+    def tasks(self) -> AsyncTasksResourceWithRawResponse:
+        return AsyncTasksResourceWithRawResponse(self._automations.tasks)
+
+
+class AutomationsResourceWithStreamingResponse:
+    def __init__(self, automations: AutomationsResource) -> None:
+        self._automations = automations
+
+        self.upsert = to_streamed_response_wrapper(
+            automations.upsert,
+        )
+
+    @cached_property
+    def services(self) -> ServicesResourceWithStreamingResponse:
+        return ServicesResourceWithStreamingResponse(self._automations.services)
+
+    @cached_property
+    def tasks(self) -> TasksResourceWithStreamingResponse:
+        return TasksResourceWithStreamingResponse(self._automations.tasks)
+
+
+class AsyncAutomationsResourceWithStreamingResponse:
+    def __init__(self, automations: AsyncAutomationsResource) -> None:
+        self._automations = automations
+
+        self.upsert = async_to_streamed_response_wrapper(
+            automations.upsert,
+        )
+
+    @cached_property
+    def services(self) -> AsyncServicesResourceWithStreamingResponse:
+        return AsyncServicesResourceWithStreamingResponse(self._automations.services)
+
+    @cached_property
+    def tasks(self) -> AsyncTasksResourceWithStreamingResponse:
+        return AsyncTasksResourceWithStreamingResponse(self._automations.tasks)
diff --git a/src/gitpod/resources/environments/automations/services.py b/src/gitpod/resources/environments/automations/services.py
new file mode 100644
index 0000000..0083cfb
--- /dev/null
+++ b/src/gitpod/resources/environments/automations/services.py
@@ -0,0 +1,1136 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ....pagination import SyncServicesPage, AsyncServicesPage
+from ...._base_client import AsyncPaginator, make_request_options
+from ....types.environments.automations import (
+    service_list_params,
+    service_stop_params,
+    service_start_params,
+    service_create_params,
+    service_delete_params,
+    service_update_params,
+    service_retrieve_params,
+)
+from ....types.environments.automations.service import Service
+from ....types.environments.automations.service_spec_param import ServiceSpecParam
+from ....types.environments.automations.service_metadata_param import ServiceMetadataParam
+from ....types.environments.automations.service_create_response import ServiceCreateResponse
+from ....types.environments.automations.service_retrieve_response import ServiceRetrieveResponse
+
+__all__ = ["ServicesResource", "AsyncServicesResource"]
+
+
+class ServicesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> ServicesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return ServicesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ServicesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return ServicesResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        metadata: ServiceMetadataParam | NotGiven = NOT_GIVEN,
+        spec: ServiceSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ServiceCreateResponse:
+        """
+        Creates a new automation service for an environment.
+
+        Use this method to:
+
+        - Set up long-running services
+        - Configure service triggers
+        - Define service dependencies
+        - Specify runtime environments
+
+        ### Examples
+
+        - Create basic service:
+
+          Creates a simple service with start command.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "web-server"
+            name: "Web Server"
+            description: "Runs the development web server"
+            triggeredBy:
+              - postDevcontainerStart: true
+          spec:
+            commands:
+              start: "npm run dev"
+              ready: "curl -s http://localhost:3000"
+          ```
+
+        - Create Docker-based service:
+
+          Creates a service running in a specific container.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "redis"
+            name: "Redis Server"
+            description: "Redis cache service"
+          spec:
+            commands:
+              start: "redis-server"
+            runsOn:
+              docker:
+                image: "redis:7"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/CreateService",
+            body=maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                service_create_params.ServiceCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ServiceCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ServiceRetrieveResponse:
+        """
+        Gets details about a specific automation service.
+
+        Use this method to:
+
+        - Check service status
+        - View service configuration
+        - Monitor service health
+        - Retrieve service metadata
+
+        ### Examples
+
+        - Get service details:
+
+          Retrieves information about a specific service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/GetService",
+            body=maybe_transform({"id": id}, service_retrieve_params.ServiceRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ServiceRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        metadata: service_update_params.Metadata | NotGiven = NOT_GIVEN,
+        spec: service_update_params.Spec | NotGiven = NOT_GIVEN,
+        status: service_update_params.Status | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an automation service configuration.
+
+        Use this method to:
+
+        - Modify service commands
+        - Update triggers
+        - Change runtime settings
+        - Adjust dependencies
+
+        ### Examples
+
+        - Update commands:
+
+          Changes service start and ready commands.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          spec:
+            commands:
+              start: "npm run start:dev"
+              ready: "curl -s http://localhost:8080"
+          ```
+
+        - Update triggers:
+
+          Modifies when the service starts.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          metadata:
+            triggeredBy:
+              trigger:
+                - postDevcontainerStart: true
+                - manual: true
+          ```
+
+        Args:
+          spec: Changing the spec of a service is a complex operation. The spec of a service can
+              only be updated if the service is in a stopped state. If the service is running,
+              it must be stopped first.
+
+          status: Service status updates are only expected from the executing environment. As a
+              client of this API you are not expected to provide this field. Updating this
+              field requires the `environmentservice:update_status` permission.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/UpdateService",
+            body=maybe_transform(
+                {
+                    "id": id,
+                    "metadata": metadata,
+                    "spec": spec,
+                    "status": status,
+                },
+                service_update_params.ServiceUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: service_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: service_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncServicesPage[Service]:
+        """
+        Lists automation services with optional filtering.
+
+        Use this method to:
+
+        - View all services in an environment
+        - Filter services by reference
+        - Monitor service status
+
+        ### Examples
+
+        - List environment services:
+
+          Shows all services for an environment.
+
+          ```yaml
+          filter:
+            environmentIds: ["07e03a28-65a5-4d98-b532-8ea67b188048"]
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by reference:
+
+          Lists services matching specific references.
+
+          ```yaml
+          filter:
+            references: ["web-server", "database"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing services
+
+          pagination: pagination contains the pagination options for listing services
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentAutomationService/ListServices",
+            page=SyncServicesPage[Service],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                service_list_params.ServiceListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    service_list_params.ServiceListParams,
+                ),
+            ),
+            model=Service,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        force: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """Deletes an automation service.
+
+        This call does not block until the service is
+        deleted. If the service is not stopped it will be stopped before deletion.
+
+        Use this method to:
+
+        - Remove unused services
+        - Clean up service configurations
+        - Stop and delete services
+
+        ### Examples
+
+        - Delete service:
+
+          Removes a service after stopping it.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          force: false
+          ```
+
+        - Force delete:
+
+          Immediately removes a service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          force: true
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/DeleteService",
+            body=maybe_transform(
+                {
+                    "id": id,
+                    "force": force,
+                },
+                service_delete_params.ServiceDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def start(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """Starts an automation service.
+
+        This call does not block until the service is
+        started. This call will not error if the service is already running or has been
+        started.
+
+        Use this method to:
+
+        - Start stopped services
+        - Resume service operations
+        - Trigger service initialization
+
+        ### Examples
+
+        - Start service:
+
+          Starts a previously stopped service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StartService",
+            body=maybe_transform({"id": id}, service_start_params.ServiceStartParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def stop(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """Stops an automation service.
+
+        This call does not block until the service is
+        stopped. This call will not error if the service is already stopped or has been
+        stopped.
+
+        Use this method to:
+
+        - Pause service operations
+        - Gracefully stop services
+        - Prepare for updates
+
+        ### Examples
+
+        - Stop service:
+
+          Gracefully stops a running service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StopService",
+            body=maybe_transform({"id": id}, service_stop_params.ServiceStopParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncServicesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncServicesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncServicesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncServicesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncServicesResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        metadata: ServiceMetadataParam | NotGiven = NOT_GIVEN,
+        spec: ServiceSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ServiceCreateResponse:
+        """
+        Creates a new automation service for an environment.
+
+        Use this method to:
+
+        - Set up long-running services
+        - Configure service triggers
+        - Define service dependencies
+        - Specify runtime environments
+
+        ### Examples
+
+        - Create basic service:
+
+          Creates a simple service with start command.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "web-server"
+            name: "Web Server"
+            description: "Runs the development web server"
+            triggeredBy:
+              - postDevcontainerStart: true
+          spec:
+            commands:
+              start: "npm run dev"
+              ready: "curl -s http://localhost:3000"
+          ```
+
+        - Create Docker-based service:
+
+          Creates a service running in a specific container.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "redis"
+            name: "Redis Server"
+            description: "Redis cache service"
+          spec:
+            commands:
+              start: "redis-server"
+            runsOn:
+              docker:
+                image: "redis:7"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/CreateService",
+            body=await async_maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                service_create_params.ServiceCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ServiceCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ServiceRetrieveResponse:
+        """
+        Gets details about a specific automation service.
+
+        Use this method to:
+
+        - Check service status
+        - View service configuration
+        - Monitor service health
+        - Retrieve service metadata
+
+        ### Examples
+
+        - Get service details:
+
+          Retrieves information about a specific service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/GetService",
+            body=await async_maybe_transform({"id": id}, service_retrieve_params.ServiceRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ServiceRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        metadata: service_update_params.Metadata | NotGiven = NOT_GIVEN,
+        spec: service_update_params.Spec | NotGiven = NOT_GIVEN,
+        status: service_update_params.Status | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an automation service configuration.
+
+        Use this method to:
+
+        - Modify service commands
+        - Update triggers
+        - Change runtime settings
+        - Adjust dependencies
+
+        ### Examples
+
+        - Update commands:
+
+          Changes service start and ready commands.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          spec:
+            commands:
+              start: "npm run start:dev"
+              ready: "curl -s http://localhost:8080"
+          ```
+
+        - Update triggers:
+
+          Modifies when the service starts.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          metadata:
+            triggeredBy:
+              trigger:
+                - postDevcontainerStart: true
+                - manual: true
+          ```
+
+        Args:
+          spec: Changing the spec of a service is a complex operation. The spec of a service can
+              only be updated if the service is in a stopped state. If the service is running,
+              it must be stopped first.
+
+          status: Service status updates are only expected from the executing environment. As a
+              client of this API you are not expected to provide this field. Updating this
+              field requires the `environmentservice:update_status` permission.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/UpdateService",
+            body=await async_maybe_transform(
+                {
+                    "id": id,
+                    "metadata": metadata,
+                    "spec": spec,
+                    "status": status,
+                },
+                service_update_params.ServiceUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: service_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: service_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Service, AsyncServicesPage[Service]]:
+        """
+        Lists automation services with optional filtering.
+
+        Use this method to:
+
+        - View all services in an environment
+        - Filter services by reference
+        - Monitor service status
+
+        ### Examples
+
+        - List environment services:
+
+          Shows all services for an environment.
+
+          ```yaml
+          filter:
+            environmentIds: ["07e03a28-65a5-4d98-b532-8ea67b188048"]
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by reference:
+
+          Lists services matching specific references.
+
+          ```yaml
+          filter:
+            references: ["web-server", "database"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing services
+
+          pagination: pagination contains the pagination options for listing services
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentAutomationService/ListServices",
+            page=AsyncServicesPage[Service],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                service_list_params.ServiceListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    service_list_params.ServiceListParams,
+                ),
+            ),
+            model=Service,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        force: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """Deletes an automation service.
+
+        This call does not block until the service is
+        deleted. If the service is not stopped it will be stopped before deletion.
+
+        Use this method to:
+
+        - Remove unused services
+        - Clean up service configurations
+        - Stop and delete services
+
+        ### Examples
+
+        - Delete service:
+
+          Removes a service after stopping it.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          force: false
+          ```
+
+        - Force delete:
+
+          Immediately removes a service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          force: true
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/DeleteService",
+            body=await async_maybe_transform(
+                {
+                    "id": id,
+                    "force": force,
+                },
+                service_delete_params.ServiceDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def start(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """Starts an automation service.
+
+        This call does not block until the service is
+        started. This call will not error if the service is already running or has been
+        started.
+
+        Use this method to:
+
+        - Start stopped services
+        - Resume service operations
+        - Trigger service initialization
+
+        ### Examples
+
+        - Start service:
+
+          Starts a previously stopped service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StartService",
+            body=await async_maybe_transform({"id": id}, service_start_params.ServiceStartParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def stop(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """Stops an automation service.
+
+        This call does not block until the service is
+        stopped. This call will not error if the service is already stopped or has been
+        stopped.
+
+        Use this method to:
+
+        - Pause service operations
+        - Gracefully stop services
+        - Prepare for updates
+
+        ### Examples
+
+        - Stop service:
+
+          Gracefully stops a running service.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StopService",
+            body=await async_maybe_transform({"id": id}, service_stop_params.ServiceStopParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class ServicesResourceWithRawResponse:
+    def __init__(self, services: ServicesResource) -> None:
+        self._services = services
+
+        self.create = to_raw_response_wrapper(
+            services.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            services.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            services.update,
+        )
+        self.list = to_raw_response_wrapper(
+            services.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            services.delete,
+        )
+        self.start = to_raw_response_wrapper(
+            services.start,
+        )
+        self.stop = to_raw_response_wrapper(
+            services.stop,
+        )
+
+
+class AsyncServicesResourceWithRawResponse:
+    def __init__(self, services: AsyncServicesResource) -> None:
+        self._services = services
+
+        self.create = async_to_raw_response_wrapper(
+            services.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            services.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            services.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            services.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            services.delete,
+        )
+        self.start = async_to_raw_response_wrapper(
+            services.start,
+        )
+        self.stop = async_to_raw_response_wrapper(
+            services.stop,
+        )
+
+
+class ServicesResourceWithStreamingResponse:
+    def __init__(self, services: ServicesResource) -> None:
+        self._services = services
+
+        self.create = to_streamed_response_wrapper(
+            services.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            services.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            services.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            services.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            services.delete,
+        )
+        self.start = to_streamed_response_wrapper(
+            services.start,
+        )
+        self.stop = to_streamed_response_wrapper(
+            services.stop,
+        )
+
+
+class AsyncServicesResourceWithStreamingResponse:
+    def __init__(self, services: AsyncServicesResource) -> None:
+        self._services = services
+
+        self.create = async_to_streamed_response_wrapper(
+            services.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            services.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            services.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            services.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            services.delete,
+        )
+        self.start = async_to_streamed_response_wrapper(
+            services.start,
+        )
+        self.stop = async_to_streamed_response_wrapper(
+            services.stop,
+        )
diff --git a/src/gitpod/resources/environments/automations/tasks/__init__.py b/src/gitpod/resources/environments/automations/tasks/__init__.py
new file mode 100644
index 0000000..5c1a049
--- /dev/null
+++ b/src/gitpod/resources/environments/automations/tasks/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .tasks import (
+    TasksResource,
+    AsyncTasksResource,
+    TasksResourceWithRawResponse,
+    AsyncTasksResourceWithRawResponse,
+    TasksResourceWithStreamingResponse,
+    AsyncTasksResourceWithStreamingResponse,
+)
+from .executions import (
+    ExecutionsResource,
+    AsyncExecutionsResource,
+    ExecutionsResourceWithRawResponse,
+    AsyncExecutionsResourceWithRawResponse,
+    ExecutionsResourceWithStreamingResponse,
+    AsyncExecutionsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "ExecutionsResource",
+    "AsyncExecutionsResource",
+    "ExecutionsResourceWithRawResponse",
+    "AsyncExecutionsResourceWithRawResponse",
+    "ExecutionsResourceWithStreamingResponse",
+    "AsyncExecutionsResourceWithStreamingResponse",
+    "TasksResource",
+    "AsyncTasksResource",
+    "TasksResourceWithRawResponse",
+    "AsyncTasksResourceWithRawResponse",
+    "TasksResourceWithStreamingResponse",
+    "AsyncTasksResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/environments/automations/tasks/executions.py b/src/gitpod/resources/environments/automations/tasks/executions.py
new file mode 100644
index 0000000..3fff111
--- /dev/null
+++ b/src/gitpod/resources/environments/automations/tasks/executions.py
@@ -0,0 +1,497 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ....._utils import maybe_transform, async_maybe_transform
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from .....pagination import SyncTaskExecutionsPage, AsyncTaskExecutionsPage
+from ....._base_client import AsyncPaginator, make_request_options
+from .....types.shared.task_execution import TaskExecution
+from .....types.environments.automations.tasks import (
+    execution_list_params,
+    execution_stop_params,
+    execution_retrieve_params,
+)
+from .....types.environments.automations.tasks.execution_retrieve_response import ExecutionRetrieveResponse
+
+__all__ = ["ExecutionsResource", "AsyncExecutionsResource"]
+
+
+class ExecutionsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> ExecutionsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return ExecutionsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ExecutionsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return ExecutionsResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ExecutionRetrieveResponse:
+        """
+        Gets details about a specific task execution.
+
+        Use this method to:
+
+        - Monitor execution progress
+        - View execution logs
+        - Check execution status
+        - Debug failed executions
+
+        ### Examples
+
+        - Get execution details:
+
+          Retrieves information about a specific task execution.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/GetTaskExecution",
+            body=maybe_transform({"id": id}, execution_retrieve_params.ExecutionRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ExecutionRetrieveResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: execution_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: execution_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncTaskExecutionsPage[TaskExecution]:
+        """
+        Lists executions of automation tasks.
+
+        Use this method to:
+
+        - View task execution history
+        - Monitor running tasks
+        - Track task completion status
+
+        ### Examples
+
+        - List all executions:
+
+          Shows execution history for all tasks.
+
+          ```yaml
+          filter:
+            environmentIds: ["07e03a28-65a5-4d98-b532-8ea67b188048"]
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by phase:
+
+          Lists executions in specific phases.
+
+          ```yaml
+          filter:
+            phases: ["TASK_EXECUTION_PHASE_RUNNING", "TASK_EXECUTION_PHASE_FAILED"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing task runs
+
+          pagination: pagination contains the pagination options for listing task runs
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentAutomationService/ListTaskExecutions",
+            page=SyncTaskExecutionsPage[TaskExecution],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                execution_list_params.ExecutionListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    execution_list_params.ExecutionListParams,
+                ),
+            ),
+            model=TaskExecution,
+            method="post",
+        )
+
+    def stop(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Stops a running task execution.
+
+        Use this method to:
+
+        - Cancel long-running tasks
+        - Stop failed executions
+        - Interrupt task processing
+
+        ### Examples
+
+        - Stop execution:
+
+          Stops a running task execution.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StopTaskExecution",
+            body=maybe_transform({"id": id}, execution_stop_params.ExecutionStopParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncExecutionsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncExecutionsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncExecutionsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncExecutionsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncExecutionsResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ExecutionRetrieveResponse:
+        """
+        Gets details about a specific task execution.
+
+        Use this method to:
+
+        - Monitor execution progress
+        - View execution logs
+        - Check execution status
+        - Debug failed executions
+
+        ### Examples
+
+        - Get execution details:
+
+          Retrieves information about a specific task execution.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/GetTaskExecution",
+            body=await async_maybe_transform({"id": id}, execution_retrieve_params.ExecutionRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ExecutionRetrieveResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: execution_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: execution_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[TaskExecution, AsyncTaskExecutionsPage[TaskExecution]]:
+        """
+        Lists executions of automation tasks.
+
+        Use this method to:
+
+        - View task execution history
+        - Monitor running tasks
+        - Track task completion status
+
+        ### Examples
+
+        - List all executions:
+
+          Shows execution history for all tasks.
+
+          ```yaml
+          filter:
+            environmentIds: ["07e03a28-65a5-4d98-b532-8ea67b188048"]
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by phase:
+
+          Lists executions in specific phases.
+
+          ```yaml
+          filter:
+            phases: ["TASK_EXECUTION_PHASE_RUNNING", "TASK_EXECUTION_PHASE_FAILED"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing task runs
+
+          pagination: pagination contains the pagination options for listing task runs
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentAutomationService/ListTaskExecutions",
+            page=AsyncTaskExecutionsPage[TaskExecution],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                execution_list_params.ExecutionListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    execution_list_params.ExecutionListParams,
+                ),
+            ),
+            model=TaskExecution,
+            method="post",
+        )
+
+    async def stop(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Stops a running task execution.
+
+        Use this method to:
+
+        - Cancel long-running tasks
+        - Stop failed executions
+        - Interrupt task processing
+
+        ### Examples
+
+        - Stop execution:
+
+          Stops a running task execution.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StopTaskExecution",
+            body=await async_maybe_transform({"id": id}, execution_stop_params.ExecutionStopParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class ExecutionsResourceWithRawResponse:
+    def __init__(self, executions: ExecutionsResource) -> None:
+        self._executions = executions
+
+        self.retrieve = to_raw_response_wrapper(
+            executions.retrieve,
+        )
+        self.list = to_raw_response_wrapper(
+            executions.list,
+        )
+        self.stop = to_raw_response_wrapper(
+            executions.stop,
+        )
+
+
+class AsyncExecutionsResourceWithRawResponse:
+    def __init__(self, executions: AsyncExecutionsResource) -> None:
+        self._executions = executions
+
+        self.retrieve = async_to_raw_response_wrapper(
+            executions.retrieve,
+        )
+        self.list = async_to_raw_response_wrapper(
+            executions.list,
+        )
+        self.stop = async_to_raw_response_wrapper(
+            executions.stop,
+        )
+
+
+class ExecutionsResourceWithStreamingResponse:
+    def __init__(self, executions: ExecutionsResource) -> None:
+        self._executions = executions
+
+        self.retrieve = to_streamed_response_wrapper(
+            executions.retrieve,
+        )
+        self.list = to_streamed_response_wrapper(
+            executions.list,
+        )
+        self.stop = to_streamed_response_wrapper(
+            executions.stop,
+        )
+
+
+class AsyncExecutionsResourceWithStreamingResponse:
+    def __init__(self, executions: AsyncExecutionsResource) -> None:
+        self._executions = executions
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            executions.retrieve,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            executions.list,
+        )
+        self.stop = async_to_streamed_response_wrapper(
+            executions.stop,
+        )
diff --git a/src/gitpod/resources/environments/automations/tasks/tasks.py b/src/gitpod/resources/environments/automations/tasks/tasks.py
new file mode 100644
index 0000000..b8a1b22
--- /dev/null
+++ b/src/gitpod/resources/environments/automations/tasks/tasks.py
@@ -0,0 +1,990 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+
+import httpx
+
+from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ....._utils import maybe_transform, async_maybe_transform
+from .executions import (
+    ExecutionsResource,
+    AsyncExecutionsResource,
+    ExecutionsResourceWithRawResponse,
+    AsyncExecutionsResourceWithRawResponse,
+    ExecutionsResourceWithStreamingResponse,
+    AsyncExecutionsResourceWithStreamingResponse,
+)
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from .....pagination import SyncTasksPage, AsyncTasksPage
+from ....._base_client import AsyncPaginator, make_request_options
+from .....types.shared.task import Task
+from .....types.shared_params.task_spec import TaskSpec
+from .....types.environments.automations import (
+    task_list_params,
+    task_start_params,
+    task_create_params,
+    task_delete_params,
+    task_update_params,
+    task_retrieve_params,
+)
+from .....types.shared_params.task_metadata import TaskMetadata
+from .....types.environments.automations.task_start_response import TaskStartResponse
+from .....types.environments.automations.task_create_response import TaskCreateResponse
+from .....types.environments.automations.task_retrieve_response import TaskRetrieveResponse
+
+__all__ = ["TasksResource", "AsyncTasksResource"]
+
+
+class TasksResource(SyncAPIResource):
+    @cached_property
+    def executions(self) -> ExecutionsResource:
+        return ExecutionsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> TasksResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return TasksResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> TasksResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return TasksResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        depends_on: List[str] | NotGiven = NOT_GIVEN,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        metadata: TaskMetadata | NotGiven = NOT_GIVEN,
+        spec: TaskSpec | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TaskCreateResponse:
+        """
+        Creates a new automation task.
+
+        Use this method to:
+
+        - Define one-off or scheduled tasks
+        - Set up build or test automation
+        - Configure task dependencies
+        - Specify execution environments
+
+        ### Examples
+
+        - Create basic task:
+
+          Creates a simple build task.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "build"
+            name: "Build Project"
+            description: "Builds the project artifacts"
+            triggeredBy:
+              - postEnvironmentStart: true
+          spec:
+            command: "npm run build"
+          ```
+
+        - Create task with dependencies:
+
+          Creates a task that depends on other services.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "test"
+            name: "Run Tests"
+            description: "Runs the test suite"
+          spec:
+            command: "npm test"
+          dependsOn: ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/CreateTask",
+            body=maybe_transform(
+                {
+                    "depends_on": depends_on,
+                    "environment_id": environment_id,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                task_create_params.TaskCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TaskCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TaskRetrieveResponse:
+        """
+        Gets details about a specific automation task.
+
+        Use this method to:
+
+        - Check task configuration
+        - View task dependencies
+        - Monitor task status
+
+        ### Examples
+
+        - Get task details:
+
+          Retrieves information about a specific task.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/GetTask",
+            body=maybe_transform({"id": id}, task_retrieve_params.TaskRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TaskRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        depends_on: List[str] | NotGiven = NOT_GIVEN,
+        metadata: task_update_params.Metadata | NotGiven = NOT_GIVEN,
+        spec: task_update_params.Spec | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an automation task configuration.
+
+        Use this method to:
+
+        - Modify task commands
+        - Update task triggers
+        - Change dependencies
+        - Adjust execution settings
+
+        ### Examples
+
+        - Update command:
+
+          Changes the task's command.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          spec:
+            command: "npm run test:coverage"
+          ```
+
+        - Update triggers:
+
+          Modifies when the task runs.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          metadata:
+            triggeredBy:
+              trigger:
+                - postEnvironmentStart: true
+          ```
+
+        Args:
+          depends_on: dependencies specifies the IDs of the automations this task depends on.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/UpdateTask",
+            body=maybe_transform(
+                {
+                    "id": id,
+                    "depends_on": depends_on,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                task_update_params.TaskUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: task_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: task_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncTasksPage[Task]:
+        """
+        Lists automation tasks with optional filtering.
+
+        Use this method to:
+
+        - View all tasks in an environment
+        - Filter tasks by reference
+        - Monitor task status
+
+        ### Examples
+
+        - List environment tasks:
+
+          Shows all tasks for an environment.
+
+          ```yaml
+          filter:
+            environmentIds: ["07e03a28-65a5-4d98-b532-8ea67b188048"]
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by reference:
+
+          Lists tasks matching specific references.
+
+          ```yaml
+          filter:
+            references: ["build", "test"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing tasks
+
+          pagination: pagination contains the pagination options for listing tasks
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentAutomationService/ListTasks",
+            page=SyncTasksPage[Task],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                task_list_params.TaskListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    task_list_params.TaskListParams,
+                ),
+            ),
+            model=Task,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes an automation task.
+
+        Use this method to:
+
+        - Remove unused tasks
+        - Clean up task configurations
+        - Delete obsolete automations
+
+        ### Examples
+
+        - Delete task:
+
+          Removes a task and its configuration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/DeleteTask",
+            body=maybe_transform({"id": id}, task_delete_params.TaskDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def start(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TaskStartResponse:
+        """Starts a task by creating a new task execution.
+
+        This call does not block until
+        the task is started; the task will be started asynchronously.
+
+        Use this method to:
+
+        - Trigger task execution
+        - Run one-off tasks
+        - Start scheduled tasks immediately
+
+        ### Examples
+
+        - Start task:
+
+          Creates a new execution of a task.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StartTask",
+            body=maybe_transform({"id": id}, task_start_params.TaskStartParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TaskStartResponse,
+        )
+
+
+class AsyncTasksResource(AsyncAPIResource):
+    @cached_property
+    def executions(self) -> AsyncExecutionsResource:
+        return AsyncExecutionsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncTasksResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncTasksResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncTasksResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncTasksResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        depends_on: List[str] | NotGiven = NOT_GIVEN,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        metadata: TaskMetadata | NotGiven = NOT_GIVEN,
+        spec: TaskSpec | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TaskCreateResponse:
+        """
+        Creates a new automation task.
+
+        Use this method to:
+
+        - Define one-off or scheduled tasks
+        - Set up build or test automation
+        - Configure task dependencies
+        - Specify execution environments
+
+        ### Examples
+
+        - Create basic task:
+
+          Creates a simple build task.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "build"
+            name: "Build Project"
+            description: "Builds the project artifacts"
+            triggeredBy:
+              - postEnvironmentStart: true
+          spec:
+            command: "npm run build"
+          ```
+
+        - Create task with dependencies:
+
+          Creates a task that depends on other services.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          metadata:
+            reference: "test"
+            name: "Run Tests"
+            description: "Runs the test suite"
+          spec:
+            command: "npm test"
+          dependsOn: ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/CreateTask",
+            body=await async_maybe_transform(
+                {
+                    "depends_on": depends_on,
+                    "environment_id": environment_id,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                task_create_params.TaskCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TaskCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TaskRetrieveResponse:
+        """
+        Gets details about a specific automation task.
+
+        Use this method to:
+
+        - Check task configuration
+        - View task dependencies
+        - Monitor task status
+
+        ### Examples
+
+        - Get task details:
+
+          Retrieves information about a specific task.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/GetTask",
+            body=await async_maybe_transform({"id": id}, task_retrieve_params.TaskRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TaskRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        depends_on: List[str] | NotGiven = NOT_GIVEN,
+        metadata: task_update_params.Metadata | NotGiven = NOT_GIVEN,
+        spec: task_update_params.Spec | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an automation task configuration.
+
+        Use this method to:
+
+        - Modify task commands
+        - Update task triggers
+        - Change dependencies
+        - Adjust execution settings
+
+        ### Examples
+
+        - Update command:
+
+          Changes the task's command.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          spec:
+            command: "npm run test:coverage"
+          ```
+
+        - Update triggers:
+
+          Modifies when the task runs.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          metadata:
+            triggeredBy:
+              trigger:
+                - postEnvironmentStart: true
+          ```
+
+        Args:
+          depends_on: dependencies specifies the IDs of the automations this task depends on.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/UpdateTask",
+            body=await async_maybe_transform(
+                {
+                    "id": id,
+                    "depends_on": depends_on,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                task_update_params.TaskUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: task_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: task_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Task, AsyncTasksPage[Task]]:
+        """
+        Lists automation tasks with optional filtering.
+
+        Use this method to:
+
+        - View all tasks in an environment
+        - Filter tasks by reference
+        - Monitor task status
+
+        ### Examples
+
+        - List environment tasks:
+
+          Shows all tasks for an environment.
+
+          ```yaml
+          filter:
+            environmentIds: ["07e03a28-65a5-4d98-b532-8ea67b188048"]
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by reference:
+
+          Lists tasks matching specific references.
+
+          ```yaml
+          filter:
+            references: ["build", "test"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          filter: filter contains the filter options for listing tasks
+
+          pagination: pagination contains the pagination options for listing tasks
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentAutomationService/ListTasks",
+            page=AsyncTasksPage[Task],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                task_list_params.TaskListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    task_list_params.TaskListParams,
+                ),
+            ),
+            model=Task,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes an automation task.
+
+        Use this method to:
+
+        - Remove unused tasks
+        - Clean up task configurations
+        - Delete obsolete automations
+
+        ### Examples
+
+        - Delete task:
+
+          Removes a task and its configuration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/DeleteTask",
+            body=await async_maybe_transform({"id": id}, task_delete_params.TaskDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def start(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TaskStartResponse:
+        """Starts a task by creating a new task execution.
+
+        This call does not block until
+        the task is started; the task will be started asynchronously.
+
+        Use this method to:
+
+        - Trigger task execution
+        - Run one-off tasks
+        - Start scheduled tasks immediately
+
+        ### Examples
+
+        - Start task:
+
+          Creates a new execution of a task.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentAutomationService/StartTask",
+            body=await async_maybe_transform({"id": id}, task_start_params.TaskStartParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TaskStartResponse,
+        )
+
+
+class TasksResourceWithRawResponse:
+    def __init__(self, tasks: TasksResource) -> None:
+        self._tasks = tasks
+
+        self.create = to_raw_response_wrapper(
+            tasks.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            tasks.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            tasks.update,
+        )
+        self.list = to_raw_response_wrapper(
+            tasks.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            tasks.delete,
+        )
+        self.start = to_raw_response_wrapper(
+            tasks.start,
+        )
+
+    @cached_property
+    def executions(self) -> ExecutionsResourceWithRawResponse:
+        return ExecutionsResourceWithRawResponse(self._tasks.executions)
+
+
+class AsyncTasksResourceWithRawResponse:
+    def __init__(self, tasks: AsyncTasksResource) -> None:
+        self._tasks = tasks
+
+        self.create = async_to_raw_response_wrapper(
+            tasks.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            tasks.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            tasks.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            tasks.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            tasks.delete,
+        )
+        self.start = async_to_raw_response_wrapper(
+            tasks.start,
+        )
+
+    @cached_property
+    def executions(self) -> AsyncExecutionsResourceWithRawResponse:
+        return AsyncExecutionsResourceWithRawResponse(self._tasks.executions)
+
+
+class TasksResourceWithStreamingResponse:
+    def __init__(self, tasks: TasksResource) -> None:
+        self._tasks = tasks
+
+        self.create = to_streamed_response_wrapper(
+            tasks.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            tasks.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            tasks.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            tasks.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            tasks.delete,
+        )
+        self.start = to_streamed_response_wrapper(
+            tasks.start,
+        )
+
+    @cached_property
+    def executions(self) -> ExecutionsResourceWithStreamingResponse:
+        return ExecutionsResourceWithStreamingResponse(self._tasks.executions)
+
+
+class AsyncTasksResourceWithStreamingResponse:
+    def __init__(self, tasks: AsyncTasksResource) -> None:
+        self._tasks = tasks
+
+        self.create = async_to_streamed_response_wrapper(
+            tasks.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            tasks.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            tasks.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            tasks.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            tasks.delete,
+        )
+        self.start = async_to_streamed_response_wrapper(
+            tasks.start,
+        )
+
+    @cached_property
+    def executions(self) -> AsyncExecutionsResourceWithStreamingResponse:
+        return AsyncExecutionsResourceWithStreamingResponse(self._tasks.executions)
diff --git a/src/gitpod/resources/environments/classes.py b/src/gitpod/resources/environments/classes.py
new file mode 100644
index 0000000..ffe58db
--- /dev/null
+++ b/src/gitpod/resources/environments/classes.py
@@ -0,0 +1,244 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncEnvironmentClassesPage, AsyncEnvironmentClassesPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.environments import class_list_params
+from ...types.shared.environment_class import EnvironmentClass
+
+__all__ = ["ClassesResource", "AsyncClassesResource"]
+
+
+class ClassesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> ClassesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return ClassesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ClassesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return ClassesResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: class_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: class_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncEnvironmentClassesPage[EnvironmentClass]:
+        """
+        Lists available environment classes with their specifications and resource
+        limits.
+
+        Use this method to understand what types of environments you can create and
+        their capabilities. Environment classes define the compute resources and
+        features available to your environments.
+
+        ### Examples
+
+        - List all available classes:
+
+          Retrieves a list of all environment classes with their specifications.
+
+          ```yaml
+          {}
+          ```
+
+          buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE
+
+        Args:
+          pagination: pagination contains the pagination options for listing environment classes
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentService/ListEnvironmentClasses",
+            page=SyncEnvironmentClassesPage[EnvironmentClass],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                class_list_params.ClassListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    class_list_params.ClassListParams,
+                ),
+            ),
+            model=EnvironmentClass,
+            method="post",
+        )
+
+
+class AsyncClassesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncClassesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncClassesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncClassesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncClassesResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: class_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: class_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[EnvironmentClass, AsyncEnvironmentClassesPage[EnvironmentClass]]:
+        """
+        Lists available environment classes with their specifications and resource
+        limits.
+
+        Use this method to understand what types of environments you can create and
+        their capabilities. Environment classes define the compute resources and
+        features available to your environments.
+
+        ### Examples
+
+        - List all available classes:
+
+          Retrieves a list of all environment classes with their specifications.
+
+          ```yaml
+          {}
+          ```
+
+          buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE
+
+        Args:
+          pagination: pagination contains the pagination options for listing environment classes
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentService/ListEnvironmentClasses",
+            page=AsyncEnvironmentClassesPage[EnvironmentClass],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                class_list_params.ClassListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    class_list_params.ClassListParams,
+                ),
+            ),
+            model=EnvironmentClass,
+            method="post",
+        )
+
+
+class ClassesResourceWithRawResponse:
+    def __init__(self, classes: ClassesResource) -> None:
+        self._classes = classes
+
+        self.list = to_raw_response_wrapper(
+            classes.list,
+        )
+
+
+class AsyncClassesResourceWithRawResponse:
+    def __init__(self, classes: AsyncClassesResource) -> None:
+        self._classes = classes
+
+        self.list = async_to_raw_response_wrapper(
+            classes.list,
+        )
+
+
+class ClassesResourceWithStreamingResponse:
+    def __init__(self, classes: ClassesResource) -> None:
+        self._classes = classes
+
+        self.list = to_streamed_response_wrapper(
+            classes.list,
+        )
+
+
+class AsyncClassesResourceWithStreamingResponse:
+    def __init__(self, classes: AsyncClassesResource) -> None:
+        self._classes = classes
+
+        self.list = async_to_streamed_response_wrapper(
+            classes.list,
+        )
diff --git a/src/gitpod/resources/environments/environments.py b/src/gitpod/resources/environments/environments.py
new file mode 100644
index 0000000..e57316b
--- /dev/null
+++ b/src/gitpod/resources/environments/environments.py
@@ -0,0 +1,1892 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...types import (
+    environment_list_params,
+    environment_stop_params,
+    environment_start_params,
+    environment_create_params,
+    environment_delete_params,
+    environment_update_params,
+    environment_retrieve_params,
+    environment_unarchive_params,
+    environment_mark_active_params,
+    environment_create_logs_token_params,
+    environment_create_from_project_params,
+    environment_create_environment_token_params,
+)
+from .classes import (
+    ClassesResource,
+    AsyncClassesResource,
+    ClassesResourceWithRawResponse,
+    AsyncClassesResourceWithRawResponse,
+    ClassesResourceWithStreamingResponse,
+    AsyncClassesResourceWithStreamingResponse,
+)
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncEnvironmentsPage, AsyncEnvironmentsPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.environment import Environment
+from .automations.automations import (
+    AutomationsResource,
+    AsyncAutomationsResource,
+    AutomationsResourceWithRawResponse,
+    AsyncAutomationsResourceWithRawResponse,
+    AutomationsResourceWithStreamingResponse,
+    AsyncAutomationsResourceWithStreamingResponse,
+)
+from ...types.environment_spec_param import EnvironmentSpecParam
+from ...types.environment_create_response import EnvironmentCreateResponse
+from ...types.environment_retrieve_response import EnvironmentRetrieveResponse
+from ...types.environment_activity_signal_param import EnvironmentActivitySignalParam
+from ...types.environment_create_logs_token_response import EnvironmentCreateLogsTokenResponse
+from ...types.environment_create_from_project_response import EnvironmentCreateFromProjectResponse
+from ...types.environment_create_environment_token_response import EnvironmentCreateEnvironmentTokenResponse
+
+__all__ = ["EnvironmentsResource", "AsyncEnvironmentsResource"]
+
+
+class EnvironmentsResource(SyncAPIResource):
+    @cached_property
+    def automations(self) -> AutomationsResource:
+        return AutomationsResource(self._client)
+
+    @cached_property
+    def classes(self) -> ClassesResource:
+        return ClassesResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> EnvironmentsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return EnvironmentsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> EnvironmentsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return EnvironmentsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        spec: EnvironmentSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateResponse:
+        """Creates a development environment from a context URL (e.g.
+
+        Git repository) and
+        starts it.
+
+        The `class` field must be a valid environment class ID. You can find a list of
+        available environment classes with the `ListEnvironmentClasses` method.
+
+        ### Examples
+
+        - Create from context URL:
+
+          Creates an environment from a Git repository URL with default settings.
+
+          ```yaml
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            content:
+              initializer:
+                specs:
+                  - contextUrl:
+                      url: "https://github.com/gitpod-io/gitpod"
+          ```
+
+        - Create from Git repository:
+
+          Creates an environment from a Git repository with specific branch targeting.
+
+          ```yaml
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            content:
+              initializer:
+                specs:
+                  - git:
+                      remoteUri: "https://github.com/gitpod-io/gitpod"
+                      cloneTarget: "main"
+                      targetMode: "CLONE_TARGET_MODE_REMOTE_BRANCH"
+          ```
+
+        - Create with custom timeout and ports:
+
+          Creates an environment with custom inactivity timeout and exposed port
+          configuration.
+
+          ```yaml
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            content:
+              initializer:
+                specs:
+                  - contextUrl:
+                      url: "https://github.com/gitpod-io/gitpod"
+            timeout:
+              disconnected: "7200s" # 2 hours in seconds
+            ports:
+              - port: 3000
+                admission: "ADMISSION_LEVEL_EVERYONE"
+                name: "Web App"
+          ```
+
+        Args:
+          spec: spec is the configuration of the environment that's required for the to start
+              the environment
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironment",
+            body=maybe_transform({"spec": spec}, environment_create_params.EnvironmentCreateParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        environment_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentRetrieveResponse:
+        """
+        Gets details about a specific environment including its status, configuration,
+        and context URL.
+
+        Use this method to:
+
+        - Check if an environment is ready to use
+        - Get connection details for IDE and exposed ports
+        - Monitor environment health and resource usage
+        - Debug environment setup issues
+
+        ### Examples
+
+        - Get environment details:
+
+          Retrieves detailed information about a specific environment using its unique
+          identifier.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment to get
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/GetEnvironment",
+            body=maybe_transform(
+                {"environment_id": environment_id}, environment_retrieve_params.EnvironmentRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        metadata: Optional[environment_update_params.Metadata] | NotGiven = NOT_GIVEN,
+        spec: Optional[environment_update_params.Spec] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an environment's configuration while it is running.
+
+        Updates are limited to:
+
+        - Git credentials (username, email)
+        - SSH public keys
+        - Content initialization
+        - Port configurations
+        - Automation files
+        - Environment timeouts
+
+        ### Examples
+
+        - Update Git credentials:
+
+          Updates the Git configuration for the environment.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          spec:
+            content:
+              gitUsername: "example-user"
+              gitEmail: "user@example.com"
+          ```
+
+        - Add SSH public key:
+
+          Adds a new SSH public key for authentication.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          spec:
+            sshPublicKeys:
+              - id: "0194b7c1-c954-718d-91a4-9a742aa5fc11"
+                value: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+          ```
+
+        - Update content session:
+
+          Updates the content session identifier for the environment.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          spec:
+            content:
+              session: "0194b7c1-c954-718d-91a4-9a742aa5fc11"
+          ```
+
+        Note: Machine class changes require stopping the environment and creating a new
+        one.
+
+        Args:
+          environment_id: environment_id specifies which environment should be updated.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/UpdateEnvironment",
+            body=maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                environment_update_params.EnvironmentUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: environment_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: environment_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncEnvironmentsPage[Environment]:
+        """
+        Lists all environments matching the specified criteria.
+
+        Use this method to find and monitor environments across your organization.
+        Results are ordered by creation time with newest environments first.
+
+        ### Examples
+
+        - List running environments for a project:
+
+          Retrieves all running environments for a specific project with pagination.
+
+          ```yaml
+          filter:
+            statusPhases: ["ENVIRONMENT_PHASE_RUNNING"]
+            projectIds: ["b0e12f6c-4c67-429d-a4a6-d9838b5da047"]
+          pagination:
+            pageSize: 10
+          ```
+
+        - List all environments for a specific runner:
+
+          Filters environments by runner ID and creator ID.
+
+          ```yaml
+          filter:
+            runnerIds: ["e6aa9c54-89d3-42c1-ac31-bd8d8f1concentrate"]
+            creatorIds: ["f53d2330-3795-4c5d-a1f3-453121af9c60"]
+          ```
+
+        - List stopped and deleted environments:
+
+          Retrieves all environments in stopped or deleted state.
+
+          ```yaml
+          filter:
+            statusPhases: ["ENVIRONMENT_PHASE_STOPPED", "ENVIRONMENT_PHASE_DELETED"]
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentService/ListEnvironments",
+            page=SyncEnvironmentsPage[Environment],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                environment_list_params.EnvironmentListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    environment_list_params.EnvironmentListParams,
+                ),
+            ),
+            model=Environment,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        force: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Permanently deletes an environment.
+
+        Running environments are automatically stopped before deletion. If force is
+        true, the environment is deleted immediately without graceful shutdown.
+
+        ### Examples
+
+        - Delete with graceful shutdown:
+
+          Deletes an environment after gracefully stopping it.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          force: false
+          ```
+
+        - Force delete:
+
+          Immediately deletes an environment without waiting for graceful shutdown.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          force: true
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment that is going to delete.
+
+              +required
+
+          force: force indicates whether the environment should be deleted forcefully When force
+              deleting an Environment, the Environment is removed immediately and environment
+              lifecycle is not respected. Force deleting can result in data loss on the
+              environment.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/DeleteEnvironment",
+            body=maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "force": force,
+                },
+                environment_delete_params.EnvironmentDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def create_environment_token(
+        self,
+        *,
+        environment_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateEnvironmentTokenResponse:
+        """
+        Creates an access token for the environment.
+
+        Generated tokens are valid for one hour and provide environment-specific access
+        permissions. The token is scoped to a specific environment.
+
+        ### Examples
+
+        - Generate environment token:
+
+          Creates a temporary access token for accessing an environment.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment for which the access token should be
+              created.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironmentAccessToken",
+            body=maybe_transform(
+                {"environment_id": environment_id},
+                environment_create_environment_token_params.EnvironmentCreateEnvironmentTokenParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateEnvironmentTokenResponse,
+        )
+
+    def create_from_project(
+        self,
+        *,
+        project_id: str | NotGiven = NOT_GIVEN,
+        spec: EnvironmentSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateFromProjectResponse:
+        """
+        Creates an environment from an existing project configuration and starts it.
+
+        This method uses project settings as defaults but allows overriding specific
+        configurations. Project settings take precedence over default configurations,
+        while custom specifications in the request override project settings.
+
+        ### Examples
+
+        - Create with project defaults:
+
+          Creates an environment using all default settings from the project
+          configuration.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        - Create with custom compute resources:
+
+          Creates an environment from project with custom machine class and timeout
+          settings.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            timeout:
+              disconnected: "14400s" # 4 hours in seconds
+          ```
+
+        Args:
+          spec: Spec is the configuration of the environment that's required for the runner to
+              start the environment Configuration already defined in the Project will override
+              parts of the spec, if set
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironmentFromProject",
+            body=maybe_transform(
+                {
+                    "project_id": project_id,
+                    "spec": spec,
+                },
+                environment_create_from_project_params.EnvironmentCreateFromProjectParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateFromProjectResponse,
+        )
+
+    def create_logs_token(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateLogsTokenResponse:
+        """
+        Creates an access token for retrieving environment logs.
+
+        Generated tokens are valid for one hour and provide read-only access to the
+        environment's logs.
+
+        ### Examples
+
+        - Generate logs token:
+
+          Creates a temporary access token for retrieving environment logs.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment for which the logs token should be
+              created.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironmentLogsToken",
+            body=maybe_transform(
+                {"environment_id": environment_id},
+                environment_create_logs_token_params.EnvironmentCreateLogsTokenParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateLogsTokenResponse,
+        )
+
+    def mark_active(
+        self,
+        *,
+        activity_signal: EnvironmentActivitySignalParam | NotGiven = NOT_GIVEN,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Records environment activity to prevent automatic shutdown.
+
+        Activity signals should be sent every 5 minutes while the environment is
+        actively being used. The source must be between 3-80 characters.
+
+        ### Examples
+
+        - Signal VS Code activity:
+
+          Records VS Code editor activity to prevent environment shutdown.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          activitySignal:
+            source: "VS Code"
+            timestamp: "2025-02-12T14:30:00Z"
+          ```
+
+        Args:
+          activity_signal: activity_signal specifies the activity.
+
+          environment_id: The ID of the environment to update activity for.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/MarkEnvironmentActive",
+            body=maybe_transform(
+                {
+                    "activity_signal": activity_signal,
+                    "environment_id": environment_id,
+                },
+                environment_mark_active_params.EnvironmentMarkActiveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def start(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Starts a stopped environment.
+
+        Use this method to resume work on a previously stopped environment. The
+        environment retains its configuration and workspace content from when it was
+        stopped.
+
+        ### Examples
+
+        - Start an environment:
+
+          Resumes a previously stopped environment with its existing configuration.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies which environment should be started.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/StartEnvironment",
+            body=maybe_transform({"environment_id": environment_id}, environment_start_params.EnvironmentStartParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def stop(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Stops a running environment.
+
+        Use this method to pause work while preserving the environment's state. The
+        environment can be resumed later using StartEnvironment.
+
+        ### Examples
+
+        - Stop an environment:
+
+          Gracefully stops a running environment while preserving its state.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies which environment should be stopped.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/StopEnvironment",
+            body=maybe_transform({"environment_id": environment_id}, environment_stop_params.EnvironmentStopParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def unarchive(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Unarchives an environment.
+
+        ### Examples
+
+        - Unarchive an environment:
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment to unarchive.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.EnvironmentService/UnarchiveEnvironment",
+            body=maybe_transform(
+                {"environment_id": environment_id}, environment_unarchive_params.EnvironmentUnarchiveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncEnvironmentsResource(AsyncAPIResource):
+    @cached_property
+    def automations(self) -> AsyncAutomationsResource:
+        return AsyncAutomationsResource(self._client)
+
+    @cached_property
+    def classes(self) -> AsyncClassesResource:
+        return AsyncClassesResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncEnvironmentsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncEnvironmentsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncEnvironmentsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncEnvironmentsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        spec: EnvironmentSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateResponse:
+        """Creates a development environment from a context URL (e.g.
+
+        Git repository) and
+        starts it.
+
+        The `class` field must be a valid environment class ID. You can find a list of
+        available environment classes with the `ListEnvironmentClasses` method.
+
+        ### Examples
+
+        - Create from context URL:
+
+          Creates an environment from a Git repository URL with default settings.
+
+          ```yaml
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            content:
+              initializer:
+                specs:
+                  - contextUrl:
+                      url: "https://github.com/gitpod-io/gitpod"
+          ```
+
+        - Create from Git repository:
+
+          Creates an environment from a Git repository with specific branch targeting.
+
+          ```yaml
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            content:
+              initializer:
+                specs:
+                  - git:
+                      remoteUri: "https://github.com/gitpod-io/gitpod"
+                      cloneTarget: "main"
+                      targetMode: "CLONE_TARGET_MODE_REMOTE_BRANCH"
+          ```
+
+        - Create with custom timeout and ports:
+
+          Creates an environment with custom inactivity timeout and exposed port
+          configuration.
+
+          ```yaml
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            content:
+              initializer:
+                specs:
+                  - contextUrl:
+                      url: "https://github.com/gitpod-io/gitpod"
+            timeout:
+              disconnected: "7200s" # 2 hours in seconds
+            ports:
+              - port: 3000
+                admission: "ADMISSION_LEVEL_EVERYONE"
+                name: "Web App"
+          ```
+
+        Args:
+          spec: spec is the configuration of the environment that's required for the to start
+              the environment
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironment",
+            body=await async_maybe_transform({"spec": spec}, environment_create_params.EnvironmentCreateParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        environment_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentRetrieveResponse:
+        """
+        Gets details about a specific environment including its status, configuration,
+        and context URL.
+
+        Use this method to:
+
+        - Check if an environment is ready to use
+        - Get connection details for IDE and exposed ports
+        - Monitor environment health and resource usage
+        - Debug environment setup issues
+
+        ### Examples
+
+        - Get environment details:
+
+          Retrieves detailed information about a specific environment using its unique
+          identifier.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment to get
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/GetEnvironment",
+            body=await async_maybe_transform(
+                {"environment_id": environment_id}, environment_retrieve_params.EnvironmentRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        metadata: Optional[environment_update_params.Metadata] | NotGiven = NOT_GIVEN,
+        spec: Optional[environment_update_params.Spec] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an environment's configuration while it is running.
+
+        Updates are limited to:
+
+        - Git credentials (username, email)
+        - SSH public keys
+        - Content initialization
+        - Port configurations
+        - Automation files
+        - Environment timeouts
+
+        ### Examples
+
+        - Update Git credentials:
+
+          Updates the Git configuration for the environment.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          spec:
+            content:
+              gitUsername: "example-user"
+              gitEmail: "user@example.com"
+          ```
+
+        - Add SSH public key:
+
+          Adds a new SSH public key for authentication.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          spec:
+            sshPublicKeys:
+              - id: "0194b7c1-c954-718d-91a4-9a742aa5fc11"
+                value: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+          ```
+
+        - Update content session:
+
+          Updates the content session identifier for the environment.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          spec:
+            content:
+              session: "0194b7c1-c954-718d-91a4-9a742aa5fc11"
+          ```
+
+        Note: Machine class changes require stopping the environment and creating a new
+        one.
+
+        Args:
+          environment_id: environment_id specifies which environment should be updated.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/UpdateEnvironment",
+            body=await async_maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "metadata": metadata,
+                    "spec": spec,
+                },
+                environment_update_params.EnvironmentUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: environment_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: environment_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Environment, AsyncEnvironmentsPage[Environment]]:
+        """
+        Lists all environments matching the specified criteria.
+
+        Use this method to find and monitor environments across your organization.
+        Results are ordered by creation time with newest environments first.
+
+        ### Examples
+
+        - List running environments for a project:
+
+          Retrieves all running environments for a specific project with pagination.
+
+          ```yaml
+          filter:
+            statusPhases: ["ENVIRONMENT_PHASE_RUNNING"]
+            projectIds: ["b0e12f6c-4c67-429d-a4a6-d9838b5da047"]
+          pagination:
+            pageSize: 10
+          ```
+
+        - List all environments for a specific runner:
+
+          Filters environments by runner ID and creator ID.
+
+          ```yaml
+          filter:
+            runnerIds: ["e6aa9c54-89d3-42c1-ac31-bd8d8f1concentrate"]
+            creatorIds: ["f53d2330-3795-4c5d-a1f3-453121af9c60"]
+          ```
+
+        - List stopped and deleted environments:
+
+          Retrieves all environments in stopped or deleted state.
+
+          ```yaml
+          filter:
+            statusPhases: ["ENVIRONMENT_PHASE_STOPPED", "ENVIRONMENT_PHASE_DELETED"]
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EnvironmentService/ListEnvironments",
+            page=AsyncEnvironmentsPage[Environment],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                environment_list_params.EnvironmentListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    environment_list_params.EnvironmentListParams,
+                ),
+            ),
+            model=Environment,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        force: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Permanently deletes an environment.
+
+        Running environments are automatically stopped before deletion. If force is
+        true, the environment is deleted immediately without graceful shutdown.
+
+        ### Examples
+
+        - Delete with graceful shutdown:
+
+          Deletes an environment after gracefully stopping it.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          force: false
+          ```
+
+        - Force delete:
+
+          Immediately deletes an environment without waiting for graceful shutdown.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          force: true
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment that is going to delete.
+
+              +required
+
+          force: force indicates whether the environment should be deleted forcefully When force
+              deleting an Environment, the Environment is removed immediately and environment
+              lifecycle is not respected. Force deleting can result in data loss on the
+              environment.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/DeleteEnvironment",
+            body=await async_maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "force": force,
+                },
+                environment_delete_params.EnvironmentDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def create_environment_token(
+        self,
+        *,
+        environment_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateEnvironmentTokenResponse:
+        """
+        Creates an access token for the environment.
+
+        Generated tokens are valid for one hour and provide environment-specific access
+        permissions. The token is scoped to a specific environment.
+
+        ### Examples
+
+        - Generate environment token:
+
+          Creates a temporary access token for accessing an environment.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment for which the access token should be
+              created.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironmentAccessToken",
+            body=await async_maybe_transform(
+                {"environment_id": environment_id},
+                environment_create_environment_token_params.EnvironmentCreateEnvironmentTokenParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateEnvironmentTokenResponse,
+        )
+
+    async def create_from_project(
+        self,
+        *,
+        project_id: str | NotGiven = NOT_GIVEN,
+        spec: EnvironmentSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateFromProjectResponse:
+        """
+        Creates an environment from an existing project configuration and starts it.
+
+        This method uses project settings as defaults but allows overriding specific
+        configurations. Project settings take precedence over default configurations,
+        while custom specifications in the request override project settings.
+
+        ### Examples
+
+        - Create with project defaults:
+
+          Creates an environment using all default settings from the project
+          configuration.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        - Create with custom compute resources:
+
+          Creates an environment from project with custom machine class and timeout
+          settings.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          spec:
+            machine:
+              class: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+            timeout:
+              disconnected: "14400s" # 4 hours in seconds
+          ```
+
+        Args:
+          spec: Spec is the configuration of the environment that's required for the runner to
+              start the environment Configuration already defined in the Project will override
+              parts of the spec, if set
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironmentFromProject",
+            body=await async_maybe_transform(
+                {
+                    "project_id": project_id,
+                    "spec": spec,
+                },
+                environment_create_from_project_params.EnvironmentCreateFromProjectParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateFromProjectResponse,
+        )
+
+    async def create_logs_token(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentCreateLogsTokenResponse:
+        """
+        Creates an access token for retrieving environment logs.
+
+        Generated tokens are valid for one hour and provide read-only access to the
+        environment's logs.
+
+        ### Examples
+
+        - Generate logs token:
+
+          Creates a temporary access token for retrieving environment logs.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment for which the logs token should be
+              created.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/CreateEnvironmentLogsToken",
+            body=await async_maybe_transform(
+                {"environment_id": environment_id},
+                environment_create_logs_token_params.EnvironmentCreateLogsTokenParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentCreateLogsTokenResponse,
+        )
+
+    async def mark_active(
+        self,
+        *,
+        activity_signal: EnvironmentActivitySignalParam | NotGiven = NOT_GIVEN,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Records environment activity to prevent automatic shutdown.
+
+        Activity signals should be sent every 5 minutes while the environment is
+        actively being used. The source must be between 3-80 characters.
+
+        ### Examples
+
+        - Signal VS Code activity:
+
+          Records VS Code editor activity to prevent environment shutdown.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          activitySignal:
+            source: "VS Code"
+            timestamp: "2025-02-12T14:30:00Z"
+          ```
+
+        Args:
+          activity_signal: activity_signal specifies the activity.
+
+          environment_id: The ID of the environment to update activity for.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/MarkEnvironmentActive",
+            body=await async_maybe_transform(
+                {
+                    "activity_signal": activity_signal,
+                    "environment_id": environment_id,
+                },
+                environment_mark_active_params.EnvironmentMarkActiveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def start(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Starts a stopped environment.
+
+        Use this method to resume work on a previously stopped environment. The
+        environment retains its configuration and workspace content from when it was
+        stopped.
+
+        ### Examples
+
+        - Start an environment:
+
+          Resumes a previously stopped environment with its existing configuration.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies which environment should be started.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/StartEnvironment",
+            body=await async_maybe_transform(
+                {"environment_id": environment_id}, environment_start_params.EnvironmentStartParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def stop(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Stops a running environment.
+
+        Use this method to pause work while preserving the environment's state. The
+        environment can be resumed later using StartEnvironment.
+
+        ### Examples
+
+        - Stop an environment:
+
+          Gracefully stops a running environment while preserving its state.
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies which environment should be stopped.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/StopEnvironment",
+            body=await async_maybe_transform(
+                {"environment_id": environment_id}, environment_stop_params.EnvironmentStopParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def unarchive(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Unarchives an environment.
+
+        ### Examples
+
+        - Unarchive an environment:
+
+          ```yaml
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment to unarchive.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.EnvironmentService/UnarchiveEnvironment",
+            body=await async_maybe_transform(
+                {"environment_id": environment_id}, environment_unarchive_params.EnvironmentUnarchiveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class EnvironmentsResourceWithRawResponse:
+    def __init__(self, environments: EnvironmentsResource) -> None:
+        self._environments = environments
+
+        self.create = to_raw_response_wrapper(
+            environments.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            environments.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            environments.update,
+        )
+        self.list = to_raw_response_wrapper(
+            environments.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            environments.delete,
+        )
+        self.create_environment_token = to_raw_response_wrapper(
+            environments.create_environment_token,
+        )
+        self.create_from_project = to_raw_response_wrapper(
+            environments.create_from_project,
+        )
+        self.create_logs_token = to_raw_response_wrapper(
+            environments.create_logs_token,
+        )
+        self.mark_active = to_raw_response_wrapper(
+            environments.mark_active,
+        )
+        self.start = to_raw_response_wrapper(
+            environments.start,
+        )
+        self.stop = to_raw_response_wrapper(
+            environments.stop,
+        )
+        self.unarchive = to_raw_response_wrapper(
+            environments.unarchive,
+        )
+
+    @cached_property
+    def automations(self) -> AutomationsResourceWithRawResponse:
+        return AutomationsResourceWithRawResponse(self._environments.automations)
+
+    @cached_property
+    def classes(self) -> ClassesResourceWithRawResponse:
+        return ClassesResourceWithRawResponse(self._environments.classes)
+
+
+class AsyncEnvironmentsResourceWithRawResponse:
+    def __init__(self, environments: AsyncEnvironmentsResource) -> None:
+        self._environments = environments
+
+        self.create = async_to_raw_response_wrapper(
+            environments.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            environments.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            environments.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            environments.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            environments.delete,
+        )
+        self.create_environment_token = async_to_raw_response_wrapper(
+            environments.create_environment_token,
+        )
+        self.create_from_project = async_to_raw_response_wrapper(
+            environments.create_from_project,
+        )
+        self.create_logs_token = async_to_raw_response_wrapper(
+            environments.create_logs_token,
+        )
+        self.mark_active = async_to_raw_response_wrapper(
+            environments.mark_active,
+        )
+        self.start = async_to_raw_response_wrapper(
+            environments.start,
+        )
+        self.stop = async_to_raw_response_wrapper(
+            environments.stop,
+        )
+        self.unarchive = async_to_raw_response_wrapper(
+            environments.unarchive,
+        )
+
+    @cached_property
+    def automations(self) -> AsyncAutomationsResourceWithRawResponse:
+        return AsyncAutomationsResourceWithRawResponse(self._environments.automations)
+
+    @cached_property
+    def classes(self) -> AsyncClassesResourceWithRawResponse:
+        return AsyncClassesResourceWithRawResponse(self._environments.classes)
+
+
+class EnvironmentsResourceWithStreamingResponse:
+    def __init__(self, environments: EnvironmentsResource) -> None:
+        self._environments = environments
+
+        self.create = to_streamed_response_wrapper(
+            environments.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            environments.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            environments.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            environments.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            environments.delete,
+        )
+        self.create_environment_token = to_streamed_response_wrapper(
+            environments.create_environment_token,
+        )
+        self.create_from_project = to_streamed_response_wrapper(
+            environments.create_from_project,
+        )
+        self.create_logs_token = to_streamed_response_wrapper(
+            environments.create_logs_token,
+        )
+        self.mark_active = to_streamed_response_wrapper(
+            environments.mark_active,
+        )
+        self.start = to_streamed_response_wrapper(
+            environments.start,
+        )
+        self.stop = to_streamed_response_wrapper(
+            environments.stop,
+        )
+        self.unarchive = to_streamed_response_wrapper(
+            environments.unarchive,
+        )
+
+    @cached_property
+    def automations(self) -> AutomationsResourceWithStreamingResponse:
+        return AutomationsResourceWithStreamingResponse(self._environments.automations)
+
+    @cached_property
+    def classes(self) -> ClassesResourceWithStreamingResponse:
+        return ClassesResourceWithStreamingResponse(self._environments.classes)
+
+
+class AsyncEnvironmentsResourceWithStreamingResponse:
+    def __init__(self, environments: AsyncEnvironmentsResource) -> None:
+        self._environments = environments
+
+        self.create = async_to_streamed_response_wrapper(
+            environments.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            environments.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            environments.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            environments.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            environments.delete,
+        )
+        self.create_environment_token = async_to_streamed_response_wrapper(
+            environments.create_environment_token,
+        )
+        self.create_from_project = async_to_streamed_response_wrapper(
+            environments.create_from_project,
+        )
+        self.create_logs_token = async_to_streamed_response_wrapper(
+            environments.create_logs_token,
+        )
+        self.mark_active = async_to_streamed_response_wrapper(
+            environments.mark_active,
+        )
+        self.start = async_to_streamed_response_wrapper(
+            environments.start,
+        )
+        self.stop = async_to_streamed_response_wrapper(
+            environments.stop,
+        )
+        self.unarchive = async_to_streamed_response_wrapper(
+            environments.unarchive,
+        )
+
+    @cached_property
+    def automations(self) -> AsyncAutomationsResourceWithStreamingResponse:
+        return AsyncAutomationsResourceWithStreamingResponse(self._environments.automations)
+
+    @cached_property
+    def classes(self) -> AsyncClassesResourceWithStreamingResponse:
+        return AsyncClassesResourceWithStreamingResponse(self._environments.classes)
diff --git a/src/gitpod/resources/events.py b/src/gitpod/resources/events.py
new file mode 100644
index 0000000..b28929f
--- /dev/null
+++ b/src/gitpod/resources/events.py
@@ -0,0 +1,402 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..types import event_list_params, event_watch_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncEntriesPage, AsyncEntriesPage
+from .._base_client import AsyncPaginator, make_request_options
+from .._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder
+from ..types.event_list_response import EventListResponse
+from ..types.event_watch_response import EventWatchResponse
+
+__all__ = ["EventsResource", "AsyncEventsResource"]
+
+
+class EventsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> EventsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return EventsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> EventsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return EventsResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: event_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: event_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncEntriesPage[EventListResponse]:
+        """
+        Lists audit logs with filtering and pagination options.
+
+        Use this method to:
+
+        - View audit history
+        - Track user actions
+        - Monitor system changes
+
+        ### Examples
+
+        - List all logs:
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by actor:
+
+          ```yaml
+          filter:
+            actorIds: ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]
+            actorPrincipals: ["PRINCIPAL_USER"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EventService/ListAuditLogs",
+            page=SyncEntriesPage[EventListResponse],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                event_list_params.EventListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    event_list_params.EventListParams,
+                ),
+            ),
+            model=EventListResponse,
+            method="post",
+        )
+
+    def watch(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        organization: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> JSONLDecoder[EventWatchResponse]:
+        """
+        Streams events for all projects, runners, environments, tasks, and services
+        based on the specified scope.
+
+        Use this method to:
+
+        - Monitor resource changes in real-time
+        - Track system events
+        - Receive notifications
+
+        The scope parameter determines which events to watch:
+
+        - Organization scope (default): Watch all organization-wide events including
+          projects, runners and environments. Task and service events are not included.
+          Use by setting organization=true or omitting the scope.
+        - Environment scope: Watch events for a specific environment, including its
+          tasks, task executions, and services. Use by setting environment_id to the
+          UUID of the environment to watch.
+
+        Args:
+          environment_id: Environment scope produces events for the environment itself, all tasks, task
+              executions, and services associated with that environment.
+
+          organization: Organization scope produces events for all projects, runners and environments
+              the caller can see within their organization. No task, task execution or service
+              events are produed.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "application/jsonl", **(extra_headers or {})}
+        return self._post(
+            "/gitpod.v1.EventService/WatchEvents",
+            body=maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "organization": organization,
+                },
+                event_watch_params.EventWatchParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=JSONLDecoder[EventWatchResponse],
+            stream=True,
+        )
+
+
+class AsyncEventsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncEventsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncEventsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncEventsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncEventsResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: event_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: event_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[EventListResponse, AsyncEntriesPage[EventListResponse]]:
+        """
+        Lists audit logs with filtering and pagination options.
+
+        Use this method to:
+
+        - View audit history
+        - Track user actions
+        - Monitor system changes
+
+        ### Examples
+
+        - List all logs:
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by actor:
+
+          ```yaml
+          filter:
+            actorIds: ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]
+            actorPrincipals: ["PRINCIPAL_USER"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.EventService/ListAuditLogs",
+            page=AsyncEntriesPage[EventListResponse],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                event_list_params.EventListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    event_list_params.EventListParams,
+                ),
+            ),
+            model=EventListResponse,
+            method="post",
+        )
+
+    async def watch(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        organization: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncJSONLDecoder[EventWatchResponse]:
+        """
+        Streams events for all projects, runners, environments, tasks, and services
+        based on the specified scope.
+
+        Use this method to:
+
+        - Monitor resource changes in real-time
+        - Track system events
+        - Receive notifications
+
+        The scope parameter determines which events to watch:
+
+        - Organization scope (default): Watch all organization-wide events including
+          projects, runners and environments. Task and service events are not included.
+          Use by setting organization=true or omitting the scope.
+        - Environment scope: Watch events for a specific environment, including its
+          tasks, task executions, and services. Use by setting environment_id to the
+          UUID of the environment to watch.
+
+        Args:
+          environment_id: Environment scope produces events for the environment itself, all tasks, task
+              executions, and services associated with that environment.
+
+          organization: Organization scope produces events for all projects, runners and environments
+              the caller can see within their organization. No task, task execution or service
+              events are produed.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "application/jsonl", **(extra_headers or {})}
+        return await self._post(
+            "/gitpod.v1.EventService/WatchEvents",
+            body=await async_maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "organization": organization,
+                },
+                event_watch_params.EventWatchParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AsyncJSONLDecoder[EventWatchResponse],
+            stream=True,
+        )
+
+
+class EventsResourceWithRawResponse:
+    def __init__(self, events: EventsResource) -> None:
+        self._events = events
+
+        self.list = to_raw_response_wrapper(
+            events.list,
+        )
+        self.watch = to_raw_response_wrapper(
+            events.watch,
+        )
+
+
+class AsyncEventsResourceWithRawResponse:
+    def __init__(self, events: AsyncEventsResource) -> None:
+        self._events = events
+
+        self.list = async_to_raw_response_wrapper(
+            events.list,
+        )
+        self.watch = async_to_raw_response_wrapper(
+            events.watch,
+        )
+
+
+class EventsResourceWithStreamingResponse:
+    def __init__(self, events: EventsResource) -> None:
+        self._events = events
+
+        self.list = to_streamed_response_wrapper(
+            events.list,
+        )
+        self.watch = to_streamed_response_wrapper(
+            events.watch,
+        )
+
+
+class AsyncEventsResourceWithStreamingResponse:
+    def __init__(self, events: AsyncEventsResource) -> None:
+        self._events = events
+
+        self.list = async_to_streamed_response_wrapper(
+            events.list,
+        )
+        self.watch = async_to_streamed_response_wrapper(
+            events.watch,
+        )
diff --git a/src/gitpod/resources/gateways.py b/src/gitpod/resources/gateways.py
new file mode 100644
index 0000000..9351d6a
--- /dev/null
+++ b/src/gitpod/resources/gateways.py
@@ -0,0 +1,196 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..types import gateway_list_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncGatewaysPage, AsyncGatewaysPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.shared.gateway import Gateway
+
+__all__ = ["GatewaysResource", "AsyncGatewaysResource"]
+
+
+class GatewaysResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> GatewaysResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return GatewaysResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> GatewaysResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return GatewaysResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: gateway_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncGatewaysPage[Gateway]:
+        """
+        ListGateways
+
+        Args:
+          pagination: pagination contains the pagination options for listing gateways
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.GatewayService/ListGateways",
+            page=SyncGatewaysPage[Gateway],
+            body=maybe_transform({"pagination": pagination}, gateway_list_params.GatewayListParams),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    gateway_list_params.GatewayListParams,
+                ),
+            ),
+            model=Gateway,
+            method="post",
+        )
+
+
+class AsyncGatewaysResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncGatewaysResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncGatewaysResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncGatewaysResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncGatewaysResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: gateway_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Gateway, AsyncGatewaysPage[Gateway]]:
+        """
+        ListGateways
+
+        Args:
+          pagination: pagination contains the pagination options for listing gateways
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.GatewayService/ListGateways",
+            page=AsyncGatewaysPage[Gateway],
+            body=maybe_transform({"pagination": pagination}, gateway_list_params.GatewayListParams),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    gateway_list_params.GatewayListParams,
+                ),
+            ),
+            model=Gateway,
+            method="post",
+        )
+
+
+class GatewaysResourceWithRawResponse:
+    def __init__(self, gateways: GatewaysResource) -> None:
+        self._gateways = gateways
+
+        self.list = to_raw_response_wrapper(
+            gateways.list,
+        )
+
+
+class AsyncGatewaysResourceWithRawResponse:
+    def __init__(self, gateways: AsyncGatewaysResource) -> None:
+        self._gateways = gateways
+
+        self.list = async_to_raw_response_wrapper(
+            gateways.list,
+        )
+
+
+class GatewaysResourceWithStreamingResponse:
+    def __init__(self, gateways: GatewaysResource) -> None:
+        self._gateways = gateways
+
+        self.list = to_streamed_response_wrapper(
+            gateways.list,
+        )
+
+
+class AsyncGatewaysResourceWithStreamingResponse:
+    def __init__(self, gateways: AsyncGatewaysResource) -> None:
+        self._gateways = gateways
+
+        self.list = async_to_streamed_response_wrapper(
+            gateways.list,
+        )
diff --git a/src/gitpod/resources/groups.py b/src/gitpod/resources/groups.py
new file mode 100644
index 0000000..56c681f
--- /dev/null
+++ b/src/gitpod/resources/groups.py
@@ -0,0 +1,252 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..types import group_list_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncGroupsPage, AsyncGroupsPage
+from ..types.group import Group
+from .._base_client import AsyncPaginator, make_request_options
+
+__all__ = ["GroupsResource", "AsyncGroupsResource"]
+
+
+class GroupsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> GroupsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return GroupsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> GroupsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return GroupsResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: group_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncGroupsPage[Group]:
+        """
+        Lists groups with optional pagination.
+
+        Use this method to:
+
+        - View all groups
+        - Check group memberships
+        - Monitor group configurations
+        - Audit group access
+
+        ### Examples
+
+        - List all groups:
+
+          Shows all groups with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - List with custom page size:
+
+          Shows groups with specified page size.
+
+          ```yaml
+          pagination:
+            pageSize: 50
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing groups
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.GroupService/ListGroups",
+            page=SyncGroupsPage[Group],
+            body=maybe_transform({"pagination": pagination}, group_list_params.GroupListParams),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    group_list_params.GroupListParams,
+                ),
+            ),
+            model=Group,
+            method="post",
+        )
+
+
+class AsyncGroupsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncGroupsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncGroupsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncGroupsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncGroupsResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: group_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Group, AsyncGroupsPage[Group]]:
+        """
+        Lists groups with optional pagination.
+
+        Use this method to:
+
+        - View all groups
+        - Check group memberships
+        - Monitor group configurations
+        - Audit group access
+
+        ### Examples
+
+        - List all groups:
+
+          Shows all groups with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - List with custom page size:
+
+          Shows groups with specified page size.
+
+          ```yaml
+          pagination:
+            pageSize: 50
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing groups
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.GroupService/ListGroups",
+            page=AsyncGroupsPage[Group],
+            body=maybe_transform({"pagination": pagination}, group_list_params.GroupListParams),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    group_list_params.GroupListParams,
+                ),
+            ),
+            model=Group,
+            method="post",
+        )
+
+
+class GroupsResourceWithRawResponse:
+    def __init__(self, groups: GroupsResource) -> None:
+        self._groups = groups
+
+        self.list = to_raw_response_wrapper(
+            groups.list,
+        )
+
+
+class AsyncGroupsResourceWithRawResponse:
+    def __init__(self, groups: AsyncGroupsResource) -> None:
+        self._groups = groups
+
+        self.list = async_to_raw_response_wrapper(
+            groups.list,
+        )
+
+
+class GroupsResourceWithStreamingResponse:
+    def __init__(self, groups: GroupsResource) -> None:
+        self._groups = groups
+
+        self.list = to_streamed_response_wrapper(
+            groups.list,
+        )
+
+
+class AsyncGroupsResourceWithStreamingResponse:
+    def __init__(self, groups: AsyncGroupsResource) -> None:
+        self._groups = groups
+
+        self.list = async_to_streamed_response_wrapper(
+            groups.list,
+        )
diff --git a/src/gitpod/resources/identity.py b/src/gitpod/resources/identity.py
new file mode 100644
index 0000000..25b859b
--- /dev/null
+++ b/src/gitpod/resources/identity.py
@@ -0,0 +1,475 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+
+import httpx
+
+from ..types import (
+    IDTokenVersion,
+    identity_get_id_token_params,
+    identity_exchange_token_params,
+    identity_get_authenticated_identity_params,
+)
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.id_token_version import IDTokenVersion
+from ..types.identity_get_id_token_response import IdentityGetIDTokenResponse
+from ..types.identity_exchange_token_response import IdentityExchangeTokenResponse
+from ..types.identity_get_authenticated_identity_response import IdentityGetAuthenticatedIdentityResponse
+
+__all__ = ["IdentityResource", "AsyncIdentityResource"]
+
+
+class IdentityResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> IdentityResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return IdentityResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> IdentityResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return IdentityResourceWithStreamingResponse(self)
+
+    def exchange_token(
+        self,
+        *,
+        exchange_token: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> IdentityExchangeTokenResponse:
+        """
+        Exchanges an exchange token for a new access token.
+
+        Use this method to:
+
+        - Convert exchange tokens to access tokens
+        - Obtain new access credentials
+        - Complete token exchange flows
+
+        ### Examples
+
+        - Exchange token:
+
+          Trades an exchange token for an access token.
+
+          ```yaml
+          exchangeToken: "exchange-token-value"
+          ```
+
+        Args:
+          exchange_token: exchange_token is the token to exchange
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.IdentityService/ExchangeToken",
+            body=maybe_transform(
+                {"exchange_token": exchange_token}, identity_exchange_token_params.IdentityExchangeTokenParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=IdentityExchangeTokenResponse,
+        )
+
+    def get_authenticated_identity(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> IdentityGetAuthenticatedIdentityResponse:
+        """
+        Retrieves information about the currently authenticated identity.
+
+        Use this method to:
+
+        - Get current user information
+        - Check authentication status
+        - Retrieve organization context
+        - Validate authentication principal
+
+        ### Examples
+
+        - Get current identity:
+
+          Retrieves details about the authenticated user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.IdentityService/GetAuthenticatedIdentity",
+            body=maybe_transform(
+                {"empty": empty}, identity_get_authenticated_identity_params.IdentityGetAuthenticatedIdentityParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=IdentityGetAuthenticatedIdentityResponse,
+        )
+
+    def get_id_token(
+        self,
+        *,
+        audience: List[str] | NotGiven = NOT_GIVEN,
+        version: IDTokenVersion | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> IdentityGetIDTokenResponse:
+        """
+        Gets an ID token for authenticating with other services.
+
+        Use this method to:
+
+        - Obtain authentication tokens for service-to-service calls
+        - Access protected resources
+        - Generate scoped access tokens
+
+        ### Examples
+
+        - Get token for single service:
+
+          Retrieves a token for authenticating with one service.
+
+          ```yaml
+          audience:
+            - "https://api.gitpod.io"
+          ```
+
+        - Get token for multiple services:
+
+          Retrieves a token valid for multiple services.
+
+          ```yaml
+          audience:
+            - "https://api.gitpod.io"
+            - "https://ws.gitpod.io"
+          ```
+
+        Args:
+          version: version is the version of the ID token.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.IdentityService/GetIDToken",
+            body=maybe_transform(
+                {
+                    "audience": audience,
+                    "version": version,
+                },
+                identity_get_id_token_params.IdentityGetIDTokenParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=IdentityGetIDTokenResponse,
+        )
+
+
+class AsyncIdentityResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncIdentityResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncIdentityResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncIdentityResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncIdentityResourceWithStreamingResponse(self)
+
+    async def exchange_token(
+        self,
+        *,
+        exchange_token: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> IdentityExchangeTokenResponse:
+        """
+        Exchanges an exchange token for a new access token.
+
+        Use this method to:
+
+        - Convert exchange tokens to access tokens
+        - Obtain new access credentials
+        - Complete token exchange flows
+
+        ### Examples
+
+        - Exchange token:
+
+          Trades an exchange token for an access token.
+
+          ```yaml
+          exchangeToken: "exchange-token-value"
+          ```
+
+        Args:
+          exchange_token: exchange_token is the token to exchange
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.IdentityService/ExchangeToken",
+            body=await async_maybe_transform(
+                {"exchange_token": exchange_token}, identity_exchange_token_params.IdentityExchangeTokenParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=IdentityExchangeTokenResponse,
+        )
+
+    async def get_authenticated_identity(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> IdentityGetAuthenticatedIdentityResponse:
+        """
+        Retrieves information about the currently authenticated identity.
+
+        Use this method to:
+
+        - Get current user information
+        - Check authentication status
+        - Retrieve organization context
+        - Validate authentication principal
+
+        ### Examples
+
+        - Get current identity:
+
+          Retrieves details about the authenticated user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.IdentityService/GetAuthenticatedIdentity",
+            body=await async_maybe_transform(
+                {"empty": empty}, identity_get_authenticated_identity_params.IdentityGetAuthenticatedIdentityParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=IdentityGetAuthenticatedIdentityResponse,
+        )
+
+    async def get_id_token(
+        self,
+        *,
+        audience: List[str] | NotGiven = NOT_GIVEN,
+        version: IDTokenVersion | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> IdentityGetIDTokenResponse:
+        """
+        Gets an ID token for authenticating with other services.
+
+        Use this method to:
+
+        - Obtain authentication tokens for service-to-service calls
+        - Access protected resources
+        - Generate scoped access tokens
+
+        ### Examples
+
+        - Get token for single service:
+
+          Retrieves a token for authenticating with one service.
+
+          ```yaml
+          audience:
+            - "https://api.gitpod.io"
+          ```
+
+        - Get token for multiple services:
+
+          Retrieves a token valid for multiple services.
+
+          ```yaml
+          audience:
+            - "https://api.gitpod.io"
+            - "https://ws.gitpod.io"
+          ```
+
+        Args:
+          version: version is the version of the ID token.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.IdentityService/GetIDToken",
+            body=await async_maybe_transform(
+                {
+                    "audience": audience,
+                    "version": version,
+                },
+                identity_get_id_token_params.IdentityGetIDTokenParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=IdentityGetIDTokenResponse,
+        )
+
+
+class IdentityResourceWithRawResponse:
+    def __init__(self, identity: IdentityResource) -> None:
+        self._identity = identity
+
+        self.exchange_token = to_raw_response_wrapper(
+            identity.exchange_token,
+        )
+        self.get_authenticated_identity = to_raw_response_wrapper(
+            identity.get_authenticated_identity,
+        )
+        self.get_id_token = to_raw_response_wrapper(
+            identity.get_id_token,
+        )
+
+
+class AsyncIdentityResourceWithRawResponse:
+    def __init__(self, identity: AsyncIdentityResource) -> None:
+        self._identity = identity
+
+        self.exchange_token = async_to_raw_response_wrapper(
+            identity.exchange_token,
+        )
+        self.get_authenticated_identity = async_to_raw_response_wrapper(
+            identity.get_authenticated_identity,
+        )
+        self.get_id_token = async_to_raw_response_wrapper(
+            identity.get_id_token,
+        )
+
+
+class IdentityResourceWithStreamingResponse:
+    def __init__(self, identity: IdentityResource) -> None:
+        self._identity = identity
+
+        self.exchange_token = to_streamed_response_wrapper(
+            identity.exchange_token,
+        )
+        self.get_authenticated_identity = to_streamed_response_wrapper(
+            identity.get_authenticated_identity,
+        )
+        self.get_id_token = to_streamed_response_wrapper(
+            identity.get_id_token,
+        )
+
+
+class AsyncIdentityResourceWithStreamingResponse:
+    def __init__(self, identity: AsyncIdentityResource) -> None:
+        self._identity = identity
+
+        self.exchange_token = async_to_streamed_response_wrapper(
+            identity.exchange_token,
+        )
+        self.get_authenticated_identity = async_to_streamed_response_wrapper(
+            identity.get_authenticated_identity,
+        )
+        self.get_id_token = async_to_streamed_response_wrapper(
+            identity.get_id_token,
+        )
diff --git a/src/gitpod/resources/organizations/__init__.py b/src/gitpod/resources/organizations/__init__.py
new file mode 100644
index 0000000..28e5eed
--- /dev/null
+++ b/src/gitpod/resources/organizations/__init__.py
@@ -0,0 +1,75 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .invites import (
+    InvitesResource,
+    AsyncInvitesResource,
+    InvitesResourceWithRawResponse,
+    AsyncInvitesResourceWithRawResponse,
+    InvitesResourceWithStreamingResponse,
+    AsyncInvitesResourceWithStreamingResponse,
+)
+from .policies import (
+    PoliciesResource,
+    AsyncPoliciesResource,
+    PoliciesResourceWithRawResponse,
+    AsyncPoliciesResourceWithRawResponse,
+    PoliciesResourceWithStreamingResponse,
+    AsyncPoliciesResourceWithStreamingResponse,
+)
+from .organizations import (
+    OrganizationsResource,
+    AsyncOrganizationsResource,
+    OrganizationsResourceWithRawResponse,
+    AsyncOrganizationsResourceWithRawResponse,
+    OrganizationsResourceWithStreamingResponse,
+    AsyncOrganizationsResourceWithStreamingResponse,
+)
+from .sso_configurations import (
+    SSOConfigurationsResource,
+    AsyncSSOConfigurationsResource,
+    SSOConfigurationsResourceWithRawResponse,
+    AsyncSSOConfigurationsResourceWithRawResponse,
+    SSOConfigurationsResourceWithStreamingResponse,
+    AsyncSSOConfigurationsResourceWithStreamingResponse,
+)
+from .domain_verifications import (
+    DomainVerificationsResource,
+    AsyncDomainVerificationsResource,
+    DomainVerificationsResourceWithRawResponse,
+    AsyncDomainVerificationsResourceWithRawResponse,
+    DomainVerificationsResourceWithStreamingResponse,
+    AsyncDomainVerificationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "DomainVerificationsResource",
+    "AsyncDomainVerificationsResource",
+    "DomainVerificationsResourceWithRawResponse",
+    "AsyncDomainVerificationsResourceWithRawResponse",
+    "DomainVerificationsResourceWithStreamingResponse",
+    "AsyncDomainVerificationsResourceWithStreamingResponse",
+    "InvitesResource",
+    "AsyncInvitesResource",
+    "InvitesResourceWithRawResponse",
+    "AsyncInvitesResourceWithRawResponse",
+    "InvitesResourceWithStreamingResponse",
+    "AsyncInvitesResourceWithStreamingResponse",
+    "PoliciesResource",
+    "AsyncPoliciesResource",
+    "PoliciesResourceWithRawResponse",
+    "AsyncPoliciesResourceWithRawResponse",
+    "PoliciesResourceWithStreamingResponse",
+    "AsyncPoliciesResourceWithStreamingResponse",
+    "SSOConfigurationsResource",
+    "AsyncSSOConfigurationsResource",
+    "SSOConfigurationsResourceWithRawResponse",
+    "AsyncSSOConfigurationsResourceWithRawResponse",
+    "SSOConfigurationsResourceWithStreamingResponse",
+    "AsyncSSOConfigurationsResourceWithStreamingResponse",
+    "OrganizationsResource",
+    "AsyncOrganizationsResource",
+    "OrganizationsResourceWithRawResponse",
+    "AsyncOrganizationsResourceWithRawResponse",
+    "OrganizationsResourceWithStreamingResponse",
+    "AsyncOrganizationsResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/organizations/domain_verifications.py b/src/gitpod/resources/organizations/domain_verifications.py
new file mode 100644
index 0000000..5468ac8
--- /dev/null
+++ b/src/gitpod/resources/organizations/domain_verifications.py
@@ -0,0 +1,761 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncDomainVerificationsPage, AsyncDomainVerificationsPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.organizations import (
+    domain_verification_list_params,
+    domain_verification_create_params,
+    domain_verification_delete_params,
+    domain_verification_verify_params,
+    domain_verification_retrieve_params,
+)
+from ...types.organizations.domain_verification import DomainVerification
+from ...types.organizations.domain_verification_create_response import DomainVerificationCreateResponse
+from ...types.organizations.domain_verification_verify_response import DomainVerificationVerifyResponse
+from ...types.organizations.domain_verification_retrieve_response import DomainVerificationRetrieveResponse
+
+__all__ = ["DomainVerificationsResource", "AsyncDomainVerificationsResource"]
+
+
+class DomainVerificationsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> DomainVerificationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return DomainVerificationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> DomainVerificationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return DomainVerificationsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        domain: str,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DomainVerificationCreateResponse:
+        """
+        Initiates domain verification process to enable organization features.
+
+        Use this method to:
+
+        - Start domain ownership verification
+        - Enable automatic team joining
+        - Set up SSO restrictions
+        - Configure email-based policies
+
+        ### Examples
+
+        - Verify primary domain:
+
+          Starts verification for main company domain.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          domain: "acme-corp.com"
+          ```
+
+        - Verify subsidiary domain:
+
+          Adds verification for additional company domain.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          domain: "acme-subsidiary.com"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/CreateDomainVerification",
+            body=maybe_transform(
+                {
+                    "domain": domain,
+                    "organization_id": organization_id,
+                },
+                domain_verification_create_params.DomainVerificationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DomainVerificationCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        domain_verification_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DomainVerificationRetrieveResponse:
+        """
+        Retrieves the status of a domain verification request.
+
+        Use this method to:
+
+        - Check verification progress
+        - View verification requirements
+        - Monitor domain status
+
+        ### Examples
+
+        - Get verification status:
+
+          Checks the current state of a domain verification.
+
+          ```yaml
+          domainVerificationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/GetDomainVerification",
+            body=maybe_transform(
+                {"domain_verification_id": domain_verification_id},
+                domain_verification_retrieve_params.DomainVerificationRetrieveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DomainVerificationRetrieveResponse,
+        )
+
+    def list(
+        self,
+        *,
+        organization_id: str,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: domain_verification_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncDomainVerificationsPage[DomainVerification]:
+        """
+        Lists and monitors domain verification status across an organization.
+
+        Use this method to:
+
+        - Track verification progress
+        - View all verified domains
+        - Monitor pending verifications
+        - Audit domain settings
+
+        ### Examples
+
+        - List all verifications:
+
+          Shows all domain verifications regardless of status.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List with pagination:
+
+          Retrieves next page of verifications.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.OrganizationService/ListDomainVerifications",
+            page=SyncDomainVerificationsPage[DomainVerification],
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "pagination": pagination,
+                },
+                domain_verification_list_params.DomainVerificationListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    domain_verification_list_params.DomainVerificationListParams,
+                ),
+            ),
+            model=DomainVerification,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        domain_verification_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Removes a domain verification request.
+
+        Use this method to:
+
+        - Cancel pending verifications
+        - Remove verified domains
+        - Clean up unused domain records
+
+        ### Examples
+
+        - Delete verification:
+
+          Removes a domain verification request.
+
+          ```yaml
+          domainVerificationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/DeleteDomainVerification",
+            body=maybe_transform(
+                {"domain_verification_id": domain_verification_id},
+                domain_verification_delete_params.DomainVerificationDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def verify(
+        self,
+        *,
+        domain_verification_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DomainVerificationVerifyResponse:
+        """
+        Verifies domain ownership for an organization.
+
+        Use this method to:
+
+        - Complete domain verification process
+        - Enable domain-based features
+        - Validate DNS configuration
+
+        ### Examples
+
+        - Verify domain ownership:
+
+          Verifies ownership after DNS records are configured.
+
+          ```yaml
+          domainVerificationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/VerifyDomain",
+            body=maybe_transform(
+                {"domain_verification_id": domain_verification_id},
+                domain_verification_verify_params.DomainVerificationVerifyParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DomainVerificationVerifyResponse,
+        )
+
+
+class AsyncDomainVerificationsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncDomainVerificationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncDomainVerificationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncDomainVerificationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncDomainVerificationsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        domain: str,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DomainVerificationCreateResponse:
+        """
+        Initiates domain verification process to enable organization features.
+
+        Use this method to:
+
+        - Start domain ownership verification
+        - Enable automatic team joining
+        - Set up SSO restrictions
+        - Configure email-based policies
+
+        ### Examples
+
+        - Verify primary domain:
+
+          Starts verification for main company domain.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          domain: "acme-corp.com"
+          ```
+
+        - Verify subsidiary domain:
+
+          Adds verification for additional company domain.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          domain: "acme-subsidiary.com"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/CreateDomainVerification",
+            body=await async_maybe_transform(
+                {
+                    "domain": domain,
+                    "organization_id": organization_id,
+                },
+                domain_verification_create_params.DomainVerificationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DomainVerificationCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        domain_verification_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DomainVerificationRetrieveResponse:
+        """
+        Retrieves the status of a domain verification request.
+
+        Use this method to:
+
+        - Check verification progress
+        - View verification requirements
+        - Monitor domain status
+
+        ### Examples
+
+        - Get verification status:
+
+          Checks the current state of a domain verification.
+
+          ```yaml
+          domainVerificationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/GetDomainVerification",
+            body=await async_maybe_transform(
+                {"domain_verification_id": domain_verification_id},
+                domain_verification_retrieve_params.DomainVerificationRetrieveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DomainVerificationRetrieveResponse,
+        )
+
+    def list(
+        self,
+        *,
+        organization_id: str,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: domain_verification_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[DomainVerification, AsyncDomainVerificationsPage[DomainVerification]]:
+        """
+        Lists and monitors domain verification status across an organization.
+
+        Use this method to:
+
+        - Track verification progress
+        - View all verified domains
+        - Monitor pending verifications
+        - Audit domain settings
+
+        ### Examples
+
+        - List all verifications:
+
+          Shows all domain verifications regardless of status.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List with pagination:
+
+          Retrieves next page of verifications.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.OrganizationService/ListDomainVerifications",
+            page=AsyncDomainVerificationsPage[DomainVerification],
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "pagination": pagination,
+                },
+                domain_verification_list_params.DomainVerificationListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    domain_verification_list_params.DomainVerificationListParams,
+                ),
+            ),
+            model=DomainVerification,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        domain_verification_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Removes a domain verification request.
+
+        Use this method to:
+
+        - Cancel pending verifications
+        - Remove verified domains
+        - Clean up unused domain records
+
+        ### Examples
+
+        - Delete verification:
+
+          Removes a domain verification request.
+
+          ```yaml
+          domainVerificationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/DeleteDomainVerification",
+            body=await async_maybe_transform(
+                {"domain_verification_id": domain_verification_id},
+                domain_verification_delete_params.DomainVerificationDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def verify(
+        self,
+        *,
+        domain_verification_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DomainVerificationVerifyResponse:
+        """
+        Verifies domain ownership for an organization.
+
+        Use this method to:
+
+        - Complete domain verification process
+        - Enable domain-based features
+        - Validate DNS configuration
+
+        ### Examples
+
+        - Verify domain ownership:
+
+          Verifies ownership after DNS records are configured.
+
+          ```yaml
+          domainVerificationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/VerifyDomain",
+            body=await async_maybe_transform(
+                {"domain_verification_id": domain_verification_id},
+                domain_verification_verify_params.DomainVerificationVerifyParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DomainVerificationVerifyResponse,
+        )
+
+
+class DomainVerificationsResourceWithRawResponse:
+    def __init__(self, domain_verifications: DomainVerificationsResource) -> None:
+        self._domain_verifications = domain_verifications
+
+        self.create = to_raw_response_wrapper(
+            domain_verifications.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            domain_verifications.retrieve,
+        )
+        self.list = to_raw_response_wrapper(
+            domain_verifications.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            domain_verifications.delete,
+        )
+        self.verify = to_raw_response_wrapper(
+            domain_verifications.verify,
+        )
+
+
+class AsyncDomainVerificationsResourceWithRawResponse:
+    def __init__(self, domain_verifications: AsyncDomainVerificationsResource) -> None:
+        self._domain_verifications = domain_verifications
+
+        self.create = async_to_raw_response_wrapper(
+            domain_verifications.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            domain_verifications.retrieve,
+        )
+        self.list = async_to_raw_response_wrapper(
+            domain_verifications.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            domain_verifications.delete,
+        )
+        self.verify = async_to_raw_response_wrapper(
+            domain_verifications.verify,
+        )
+
+
+class DomainVerificationsResourceWithStreamingResponse:
+    def __init__(self, domain_verifications: DomainVerificationsResource) -> None:
+        self._domain_verifications = domain_verifications
+
+        self.create = to_streamed_response_wrapper(
+            domain_verifications.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            domain_verifications.retrieve,
+        )
+        self.list = to_streamed_response_wrapper(
+            domain_verifications.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            domain_verifications.delete,
+        )
+        self.verify = to_streamed_response_wrapper(
+            domain_verifications.verify,
+        )
+
+
+class AsyncDomainVerificationsResourceWithStreamingResponse:
+    def __init__(self, domain_verifications: AsyncDomainVerificationsResource) -> None:
+        self._domain_verifications = domain_verifications
+
+        self.create = async_to_streamed_response_wrapper(
+            domain_verifications.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            domain_verifications.retrieve,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            domain_verifications.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            domain_verifications.delete,
+        )
+        self.verify = async_to_streamed_response_wrapper(
+            domain_verifications.verify,
+        )
diff --git a/src/gitpod/resources/organizations/invites.py b/src/gitpod/resources/organizations/invites.py
new file mode 100644
index 0000000..8ed2e02
--- /dev/null
+++ b/src/gitpod/resources/organizations/invites.py
@@ -0,0 +1,393 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.organizations import invite_create_params, invite_retrieve_params, invite_get_summary_params
+from ...types.organizations.invite_create_response import InviteCreateResponse
+from ...types.organizations.invite_retrieve_response import InviteRetrieveResponse
+from ...types.organizations.invite_get_summary_response import InviteGetSummaryResponse
+
+__all__ = ["InvitesResource", "AsyncInvitesResource"]
+
+
+class InvitesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> InvitesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return InvitesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> InvitesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return InvitesResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> InviteCreateResponse:
+        """Creates an invite link for joining an organization.
+
+        Any existing
+        OrganizationInvites are invalidated and can no longer be used.
+
+        Use this method to:
+
+        - Generate shareable invite links
+        - Manage team growth
+        - Control organization access
+
+        ### Examples
+
+        - Create organization invite:
+
+          Generates a new invite link for the organization.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/CreateOrganizationInvite",
+            body=maybe_transform({"organization_id": organization_id}, invite_create_params.InviteCreateParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=InviteCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> InviteRetrieveResponse:
+        """
+        GetOrganizationInvite
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/GetOrganizationInvite",
+            body=maybe_transform({"organization_id": organization_id}, invite_retrieve_params.InviteRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=InviteRetrieveResponse,
+        )
+
+    def get_summary(
+        self,
+        *,
+        invite_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> InviteGetSummaryResponse:
+        """
+        Retrieves organization details and membership info based on an invite link.
+
+        Use this method to:
+
+        - Preview organization details before joining
+        - Validate invite link authenticity
+        - Check organization size and activity
+        - View team information before accepting
+
+        ### Examples
+
+        - Get invite summary:
+
+          Retrieves organization information from an invite.
+
+          ```yaml
+          inviteId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/GetOrganizationInviteSummary",
+            body=maybe_transform({"invite_id": invite_id}, invite_get_summary_params.InviteGetSummaryParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=InviteGetSummaryResponse,
+        )
+
+
+class AsyncInvitesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncInvitesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncInvitesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncInvitesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncInvitesResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> InviteCreateResponse:
+        """Creates an invite link for joining an organization.
+
+        Any existing
+        OrganizationInvites are invalidated and can no longer be used.
+
+        Use this method to:
+
+        - Generate shareable invite links
+        - Manage team growth
+        - Control organization access
+
+        ### Examples
+
+        - Create organization invite:
+
+          Generates a new invite link for the organization.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/CreateOrganizationInvite",
+            body=await async_maybe_transform(
+                {"organization_id": organization_id}, invite_create_params.InviteCreateParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=InviteCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> InviteRetrieveResponse:
+        """
+        GetOrganizationInvite
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/GetOrganizationInvite",
+            body=await async_maybe_transform(
+                {"organization_id": organization_id}, invite_retrieve_params.InviteRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=InviteRetrieveResponse,
+        )
+
+    async def get_summary(
+        self,
+        *,
+        invite_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> InviteGetSummaryResponse:
+        """
+        Retrieves organization details and membership info based on an invite link.
+
+        Use this method to:
+
+        - Preview organization details before joining
+        - Validate invite link authenticity
+        - Check organization size and activity
+        - View team information before accepting
+
+        ### Examples
+
+        - Get invite summary:
+
+          Retrieves organization information from an invite.
+
+          ```yaml
+          inviteId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/GetOrganizationInviteSummary",
+            body=await async_maybe_transform(
+                {"invite_id": invite_id}, invite_get_summary_params.InviteGetSummaryParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=InviteGetSummaryResponse,
+        )
+
+
+class InvitesResourceWithRawResponse:
+    def __init__(self, invites: InvitesResource) -> None:
+        self._invites = invites
+
+        self.create = to_raw_response_wrapper(
+            invites.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            invites.retrieve,
+        )
+        self.get_summary = to_raw_response_wrapper(
+            invites.get_summary,
+        )
+
+
+class AsyncInvitesResourceWithRawResponse:
+    def __init__(self, invites: AsyncInvitesResource) -> None:
+        self._invites = invites
+
+        self.create = async_to_raw_response_wrapper(
+            invites.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            invites.retrieve,
+        )
+        self.get_summary = async_to_raw_response_wrapper(
+            invites.get_summary,
+        )
+
+
+class InvitesResourceWithStreamingResponse:
+    def __init__(self, invites: InvitesResource) -> None:
+        self._invites = invites
+
+        self.create = to_streamed_response_wrapper(
+            invites.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            invites.retrieve,
+        )
+        self.get_summary = to_streamed_response_wrapper(
+            invites.get_summary,
+        )
+
+
+class AsyncInvitesResourceWithStreamingResponse:
+    def __init__(self, invites: AsyncInvitesResource) -> None:
+        self._invites = invites
+
+        self.create = async_to_streamed_response_wrapper(
+            invites.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            invites.retrieve,
+        )
+        self.get_summary = async_to_streamed_response_wrapper(
+            invites.get_summary,
+        )
diff --git a/src/gitpod/resources/organizations/organizations.py b/src/gitpod/resources/organizations/organizations.py
new file mode 100644
index 0000000..0e989d8
--- /dev/null
+++ b/src/gitpod/resources/organizations/organizations.py
@@ -0,0 +1,1401 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...types import (
+    organization_join_params,
+    organization_leave_params,
+    organization_create_params,
+    organization_delete_params,
+    organization_update_params,
+    organization_retrieve_params,
+    organization_set_role_params,
+    organization_list_members_params,
+)
+from .invites import (
+    InvitesResource,
+    AsyncInvitesResource,
+    InvitesResourceWithRawResponse,
+    AsyncInvitesResourceWithRawResponse,
+    InvitesResourceWithStreamingResponse,
+    AsyncInvitesResourceWithStreamingResponse,
+)
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from .policies import (
+    PoliciesResource,
+    AsyncPoliciesResource,
+    PoliciesResourceWithRawResponse,
+    AsyncPoliciesResourceWithRawResponse,
+    PoliciesResourceWithStreamingResponse,
+    AsyncPoliciesResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncMembersPage, AsyncMembersPage
+from ..._base_client import AsyncPaginator, make_request_options
+from .sso_configurations import (
+    SSOConfigurationsResource,
+    AsyncSSOConfigurationsResource,
+    SSOConfigurationsResourceWithRawResponse,
+    AsyncSSOConfigurationsResourceWithRawResponse,
+    SSOConfigurationsResourceWithStreamingResponse,
+    AsyncSSOConfigurationsResourceWithStreamingResponse,
+)
+from .domain_verifications import (
+    DomainVerificationsResource,
+    AsyncDomainVerificationsResource,
+    DomainVerificationsResourceWithRawResponse,
+    AsyncDomainVerificationsResourceWithRawResponse,
+    DomainVerificationsResourceWithStreamingResponse,
+    AsyncDomainVerificationsResourceWithStreamingResponse,
+)
+from ...types.organization_member import OrganizationMember
+from ...types.invite_domains_param import InviteDomainsParam
+from ...types.shared.organization_role import OrganizationRole
+from ...types.organization_join_response import OrganizationJoinResponse
+from ...types.organization_create_response import OrganizationCreateResponse
+from ...types.organization_update_response import OrganizationUpdateResponse
+from ...types.organization_retrieve_response import OrganizationRetrieveResponse
+
+__all__ = ["OrganizationsResource", "AsyncOrganizationsResource"]
+
+
+class OrganizationsResource(SyncAPIResource):
+    @cached_property
+    def domain_verifications(self) -> DomainVerificationsResource:
+        return DomainVerificationsResource(self._client)
+
+    @cached_property
+    def invites(self) -> InvitesResource:
+        return InvitesResource(self._client)
+
+    @cached_property
+    def policies(self) -> PoliciesResource:
+        return PoliciesResource(self._client)
+
+    @cached_property
+    def sso_configurations(self) -> SSOConfigurationsResource:
+        return SSOConfigurationsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> OrganizationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return OrganizationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return OrganizationsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        name: str,
+        invite_accounts_with_matching_domain: bool | NotGiven = NOT_GIVEN,
+        join_organization: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationCreateResponse:
+        """
+        Creates a new organization with the specified name and settings.
+
+        Use this method to:
+
+        - Create a new organization for team collaboration
+        - Set up automatic domain-based invites for team members
+        - Join the organization immediately upon creation
+
+        ### Examples
+
+        - Create a basic organization:
+
+          Creates an organization with just a name.
+
+          ```yaml
+          name: "Acme Corp Engineering"
+          joinOrganization: true
+          ```
+
+        - Create with domain-based invites:
+
+          Creates an organization that automatically invites users with matching email
+          domains.
+
+          ```yaml
+          name: "Acme Corp"
+          joinOrganization: true
+          inviteAccountsWithMatchingDomain: true
+          ```
+
+        Args:
+          name: name is the organization name
+
+          invite_accounts_with_matching_domain: Should other Accounts with the same domain be automatically invited to the
+              organization?
+
+          join_organization: join_organization decides whether the Identity issuing this request joins the
+              org on creation
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/CreateOrganization",
+            body=maybe_transform(
+                {
+                    "name": name,
+                    "invite_accounts_with_matching_domain": invite_accounts_with_matching_domain,
+                    "join_organization": join_organization,
+                },
+                organization_create_params.OrganizationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationRetrieveResponse:
+        """
+        Gets details about a specific organization.
+
+        Use this method to:
+
+        - Retrieve organization settings and configuration
+        - Check organization membership status
+        - View domain verification settings
+
+        ### Examples
+
+        - Get organization details:
+
+          Retrieves information about a specific organization.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          organization_id: organization_id is the unique identifier of the Organization to retreive.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/GetOrganization",
+            body=maybe_transform(
+                {"organization_id": organization_id}, organization_retrieve_params.OrganizationRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        organization_id: str,
+        invite_domains: Optional[InviteDomainsParam] | NotGiven = NOT_GIVEN,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationUpdateResponse:
+        """
+        Updates an organization's settings including name, invite domains, and member
+        policies.
+
+        Use this method to:
+
+        - Modify organization display name
+        - Configure email domain restrictions
+        - Update organization-wide settings
+        - Manage member access policies
+
+        ### Examples
+
+        - Update basic settings:
+
+          Changes organization name and invite domains.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          name: "New Company Name"
+          inviteDomains:
+            domains:
+              - "company.com"
+              - "subsidiary.com"
+          ```
+
+        - Remove domain restrictions:
+
+          Clears all domain-based invite restrictions.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          inviteDomains:
+            domains: []
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to update the settings for.
+
+          invite_domains: invite_domains is the domain allowlist of the organization
+
+          name: name is the new name of the organization
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/UpdateOrganization",
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "invite_domains": invite_domains,
+                    "name": name,
+                },
+                organization_update_params.OrganizationUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationUpdateResponse,
+        )
+
+    def delete(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Permanently deletes an organization.
+
+        Use this method to:
+
+        - Remove unused organizations
+        - Clean up test organizations
+        - Complete organization migration
+
+        ### Examples
+
+        - Delete organization:
+
+          Permanently removes an organization and all its data.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to delete
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/DeleteOrganization",
+            body=maybe_transform(
+                {"organization_id": organization_id}, organization_delete_params.OrganizationDeleteParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def join(
+        self,
+        *,
+        invite_id: str | NotGiven = NOT_GIVEN,
+        organization_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationJoinResponse:
+        """
+        Allows users to join an organization through direct ID, invite link, or
+        domain-based auto-join.
+
+        Use this method to:
+
+        - Join an organization via direct ID or invite
+        - Join automatically based on email domain
+        - Accept organization invitations
+
+        ### Examples
+
+        - Join via organization ID:
+
+          Joins an organization directly when you have the ID.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        - Join via invite:
+
+          Accepts an organization invitation link.
+
+          ```yaml
+          inviteId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          invite_id: invite_id is the unique identifier of the invite to join the organization.
+
+          organization_id: organization_id is the unique identifier of the Organization to join.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/JoinOrganization",
+            body=maybe_transform(
+                {
+                    "invite_id": invite_id,
+                    "organization_id": organization_id,
+                },
+                organization_join_params.OrganizationJoinParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationJoinResponse,
+        )
+
+    def leave(
+        self,
+        *,
+        user_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Removes a user from an organization while preserving organization data.
+
+        Use this method to:
+
+        - Remove yourself from an organization
+        - Clean up inactive memberships
+        - Transfer project ownership before leaving
+        - Manage team transitions
+
+        ### Examples
+
+        - Leave organization:
+
+          Removes user from organization membership.
+
+          ```yaml
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Note: Ensure all projects and resources are transferred before leaving.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/LeaveOrganization",
+            body=maybe_transform({"user_id": user_id}, organization_leave_params.OrganizationLeaveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list_members(
+        self,
+        *,
+        organization_id: str,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: organization_list_members_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncMembersPage[OrganizationMember]:
+        """
+        Lists and filters organization members with optional pagination.
+
+        Use this method to:
+
+        - View all organization members
+        - Monitor member activity
+        - Manage team membership
+
+        ### Examples
+
+        - List active members:
+
+          Retrieves active members with pagination.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List with pagination:
+
+          Retrieves next page of members.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 50
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to list members for
+
+          pagination: pagination contains the pagination options for listing members
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.OrganizationService/ListMembers",
+            page=SyncMembersPage[OrganizationMember],
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "pagination": pagination,
+                },
+                organization_list_members_params.OrganizationListMembersParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    organization_list_members_params.OrganizationListMembersParams,
+                ),
+            ),
+            model=OrganizationMember,
+            method="post",
+        )
+
+    def set_role(
+        self,
+        *,
+        organization_id: str,
+        user_id: str,
+        role: OrganizationRole | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Manages organization membership and roles by setting a user's role within the
+        organization.
+
+        Use this method to:
+
+        - Promote members to admin role
+        - Change member permissions
+        - Demote admins to regular members
+
+        ### Examples
+
+        - Promote to admin:
+
+          Makes a user an organization administrator.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: ORGANIZATION_ROLE_ADMIN
+          ```
+
+        - Change to member:
+
+          Changes a user's role to regular member.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: ORGANIZATION_ROLE_MEMBER
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/SetRole",
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "user_id": user_id,
+                    "role": role,
+                },
+                organization_set_role_params.OrganizationSetRoleParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncOrganizationsResource(AsyncAPIResource):
+    @cached_property
+    def domain_verifications(self) -> AsyncDomainVerificationsResource:
+        return AsyncDomainVerificationsResource(self._client)
+
+    @cached_property
+    def invites(self) -> AsyncInvitesResource:
+        return AsyncInvitesResource(self._client)
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResource:
+        return AsyncPoliciesResource(self._client)
+
+    @cached_property
+    def sso_configurations(self) -> AsyncSSOConfigurationsResource:
+        return AsyncSSOConfigurationsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncOrganizationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncOrganizationsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        name: str,
+        invite_accounts_with_matching_domain: bool | NotGiven = NOT_GIVEN,
+        join_organization: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationCreateResponse:
+        """
+        Creates a new organization with the specified name and settings.
+
+        Use this method to:
+
+        - Create a new organization for team collaboration
+        - Set up automatic domain-based invites for team members
+        - Join the organization immediately upon creation
+
+        ### Examples
+
+        - Create a basic organization:
+
+          Creates an organization with just a name.
+
+          ```yaml
+          name: "Acme Corp Engineering"
+          joinOrganization: true
+          ```
+
+        - Create with domain-based invites:
+
+          Creates an organization that automatically invites users with matching email
+          domains.
+
+          ```yaml
+          name: "Acme Corp"
+          joinOrganization: true
+          inviteAccountsWithMatchingDomain: true
+          ```
+
+        Args:
+          name: name is the organization name
+
+          invite_accounts_with_matching_domain: Should other Accounts with the same domain be automatically invited to the
+              organization?
+
+          join_organization: join_organization decides whether the Identity issuing this request joins the
+              org on creation
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/CreateOrganization",
+            body=await async_maybe_transform(
+                {
+                    "name": name,
+                    "invite_accounts_with_matching_domain": invite_accounts_with_matching_domain,
+                    "join_organization": join_organization,
+                },
+                organization_create_params.OrganizationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationRetrieveResponse:
+        """
+        Gets details about a specific organization.
+
+        Use this method to:
+
+        - Retrieve organization settings and configuration
+        - Check organization membership status
+        - View domain verification settings
+
+        ### Examples
+
+        - Get organization details:
+
+          Retrieves information about a specific organization.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          organization_id: organization_id is the unique identifier of the Organization to retreive.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/GetOrganization",
+            body=await async_maybe_transform(
+                {"organization_id": organization_id}, organization_retrieve_params.OrganizationRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        organization_id: str,
+        invite_domains: Optional[InviteDomainsParam] | NotGiven = NOT_GIVEN,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationUpdateResponse:
+        """
+        Updates an organization's settings including name, invite domains, and member
+        policies.
+
+        Use this method to:
+
+        - Modify organization display name
+        - Configure email domain restrictions
+        - Update organization-wide settings
+        - Manage member access policies
+
+        ### Examples
+
+        - Update basic settings:
+
+          Changes organization name and invite domains.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          name: "New Company Name"
+          inviteDomains:
+            domains:
+              - "company.com"
+              - "subsidiary.com"
+          ```
+
+        - Remove domain restrictions:
+
+          Clears all domain-based invite restrictions.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          inviteDomains:
+            domains: []
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to update the settings for.
+
+          invite_domains: invite_domains is the domain allowlist of the organization
+
+          name: name is the new name of the organization
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/UpdateOrganization",
+            body=await async_maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "invite_domains": invite_domains,
+                    "name": name,
+                },
+                organization_update_params.OrganizationUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationUpdateResponse,
+        )
+
+    async def delete(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Permanently deletes an organization.
+
+        Use this method to:
+
+        - Remove unused organizations
+        - Clean up test organizations
+        - Complete organization migration
+
+        ### Examples
+
+        - Delete organization:
+
+          Permanently removes an organization and all its data.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to delete
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/DeleteOrganization",
+            body=await async_maybe_transform(
+                {"organization_id": organization_id}, organization_delete_params.OrganizationDeleteParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def join(
+        self,
+        *,
+        invite_id: str | NotGiven = NOT_GIVEN,
+        organization_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationJoinResponse:
+        """
+        Allows users to join an organization through direct ID, invite link, or
+        domain-based auto-join.
+
+        Use this method to:
+
+        - Join an organization via direct ID or invite
+        - Join automatically based on email domain
+        - Accept organization invitations
+
+        ### Examples
+
+        - Join via organization ID:
+
+          Joins an organization directly when you have the ID.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        - Join via invite:
+
+          Accepts an organization invitation link.
+
+          ```yaml
+          inviteId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          invite_id: invite_id is the unique identifier of the invite to join the organization.
+
+          organization_id: organization_id is the unique identifier of the Organization to join.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/JoinOrganization",
+            body=await async_maybe_transform(
+                {
+                    "invite_id": invite_id,
+                    "organization_id": organization_id,
+                },
+                organization_join_params.OrganizationJoinParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationJoinResponse,
+        )
+
+    async def leave(
+        self,
+        *,
+        user_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Removes a user from an organization while preserving organization data.
+
+        Use this method to:
+
+        - Remove yourself from an organization
+        - Clean up inactive memberships
+        - Transfer project ownership before leaving
+        - Manage team transitions
+
+        ### Examples
+
+        - Leave organization:
+
+          Removes user from organization membership.
+
+          ```yaml
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Note: Ensure all projects and resources are transferred before leaving.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/LeaveOrganization",
+            body=await async_maybe_transform({"user_id": user_id}, organization_leave_params.OrganizationLeaveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list_members(
+        self,
+        *,
+        organization_id: str,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: organization_list_members_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[OrganizationMember, AsyncMembersPage[OrganizationMember]]:
+        """
+        Lists and filters organization members with optional pagination.
+
+        Use this method to:
+
+        - View all organization members
+        - Monitor member activity
+        - Manage team membership
+
+        ### Examples
+
+        - List active members:
+
+          Retrieves active members with pagination.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List with pagination:
+
+          Retrieves next page of members.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 50
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to list members for
+
+          pagination: pagination contains the pagination options for listing members
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.OrganizationService/ListMembers",
+            page=AsyncMembersPage[OrganizationMember],
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "pagination": pagination,
+                },
+                organization_list_members_params.OrganizationListMembersParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    organization_list_members_params.OrganizationListMembersParams,
+                ),
+            ),
+            model=OrganizationMember,
+            method="post",
+        )
+
+    async def set_role(
+        self,
+        *,
+        organization_id: str,
+        user_id: str,
+        role: OrganizationRole | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Manages organization membership and roles by setting a user's role within the
+        organization.
+
+        Use this method to:
+
+        - Promote members to admin role
+        - Change member permissions
+        - Demote admins to regular members
+
+        ### Examples
+
+        - Promote to admin:
+
+          Makes a user an organization administrator.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: ORGANIZATION_ROLE_ADMIN
+          ```
+
+        - Change to member:
+
+          Changes a user's role to regular member.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: ORGANIZATION_ROLE_MEMBER
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/SetRole",
+            body=await async_maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "user_id": user_id,
+                    "role": role,
+                },
+                organization_set_role_params.OrganizationSetRoleParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class OrganizationsResourceWithRawResponse:
+    def __init__(self, organizations: OrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.create = to_raw_response_wrapper(
+            organizations.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            organizations.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            organizations.update,
+        )
+        self.delete = to_raw_response_wrapper(
+            organizations.delete,
+        )
+        self.join = to_raw_response_wrapper(
+            organizations.join,
+        )
+        self.leave = to_raw_response_wrapper(
+            organizations.leave,
+        )
+        self.list_members = to_raw_response_wrapper(
+            organizations.list_members,
+        )
+        self.set_role = to_raw_response_wrapper(
+            organizations.set_role,
+        )
+
+    @cached_property
+    def domain_verifications(self) -> DomainVerificationsResourceWithRawResponse:
+        return DomainVerificationsResourceWithRawResponse(self._organizations.domain_verifications)
+
+    @cached_property
+    def invites(self) -> InvitesResourceWithRawResponse:
+        return InvitesResourceWithRawResponse(self._organizations.invites)
+
+    @cached_property
+    def policies(self) -> PoliciesResourceWithRawResponse:
+        return PoliciesResourceWithRawResponse(self._organizations.policies)
+
+    @cached_property
+    def sso_configurations(self) -> SSOConfigurationsResourceWithRawResponse:
+        return SSOConfigurationsResourceWithRawResponse(self._organizations.sso_configurations)
+
+
+class AsyncOrganizationsResourceWithRawResponse:
+    def __init__(self, organizations: AsyncOrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.create = async_to_raw_response_wrapper(
+            organizations.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            organizations.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            organizations.update,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            organizations.delete,
+        )
+        self.join = async_to_raw_response_wrapper(
+            organizations.join,
+        )
+        self.leave = async_to_raw_response_wrapper(
+            organizations.leave,
+        )
+        self.list_members = async_to_raw_response_wrapper(
+            organizations.list_members,
+        )
+        self.set_role = async_to_raw_response_wrapper(
+            organizations.set_role,
+        )
+
+    @cached_property
+    def domain_verifications(self) -> AsyncDomainVerificationsResourceWithRawResponse:
+        return AsyncDomainVerificationsResourceWithRawResponse(self._organizations.domain_verifications)
+
+    @cached_property
+    def invites(self) -> AsyncInvitesResourceWithRawResponse:
+        return AsyncInvitesResourceWithRawResponse(self._organizations.invites)
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResourceWithRawResponse:
+        return AsyncPoliciesResourceWithRawResponse(self._organizations.policies)
+
+    @cached_property
+    def sso_configurations(self) -> AsyncSSOConfigurationsResourceWithRawResponse:
+        return AsyncSSOConfigurationsResourceWithRawResponse(self._organizations.sso_configurations)
+
+
+class OrganizationsResourceWithStreamingResponse:
+    def __init__(self, organizations: OrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.create = to_streamed_response_wrapper(
+            organizations.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            organizations.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            organizations.update,
+        )
+        self.delete = to_streamed_response_wrapper(
+            organizations.delete,
+        )
+        self.join = to_streamed_response_wrapper(
+            organizations.join,
+        )
+        self.leave = to_streamed_response_wrapper(
+            organizations.leave,
+        )
+        self.list_members = to_streamed_response_wrapper(
+            organizations.list_members,
+        )
+        self.set_role = to_streamed_response_wrapper(
+            organizations.set_role,
+        )
+
+    @cached_property
+    def domain_verifications(self) -> DomainVerificationsResourceWithStreamingResponse:
+        return DomainVerificationsResourceWithStreamingResponse(self._organizations.domain_verifications)
+
+    @cached_property
+    def invites(self) -> InvitesResourceWithStreamingResponse:
+        return InvitesResourceWithStreamingResponse(self._organizations.invites)
+
+    @cached_property
+    def policies(self) -> PoliciesResourceWithStreamingResponse:
+        return PoliciesResourceWithStreamingResponse(self._organizations.policies)
+
+    @cached_property
+    def sso_configurations(self) -> SSOConfigurationsResourceWithStreamingResponse:
+        return SSOConfigurationsResourceWithStreamingResponse(self._organizations.sso_configurations)
+
+
+class AsyncOrganizationsResourceWithStreamingResponse:
+    def __init__(self, organizations: AsyncOrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.create = async_to_streamed_response_wrapper(
+            organizations.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            organizations.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            organizations.update,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            organizations.delete,
+        )
+        self.join = async_to_streamed_response_wrapper(
+            organizations.join,
+        )
+        self.leave = async_to_streamed_response_wrapper(
+            organizations.leave,
+        )
+        self.list_members = async_to_streamed_response_wrapper(
+            organizations.list_members,
+        )
+        self.set_role = async_to_streamed_response_wrapper(
+            organizations.set_role,
+        )
+
+    @cached_property
+    def domain_verifications(self) -> AsyncDomainVerificationsResourceWithStreamingResponse:
+        return AsyncDomainVerificationsResourceWithStreamingResponse(self._organizations.domain_verifications)
+
+    @cached_property
+    def invites(self) -> AsyncInvitesResourceWithStreamingResponse:
+        return AsyncInvitesResourceWithStreamingResponse(self._organizations.invites)
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResourceWithStreamingResponse:
+        return AsyncPoliciesResourceWithStreamingResponse(self._organizations.policies)
+
+    @cached_property
+    def sso_configurations(self) -> AsyncSSOConfigurationsResourceWithStreamingResponse:
+        return AsyncSSOConfigurationsResourceWithStreamingResponse(self._organizations.sso_configurations)
diff --git a/src/gitpod/resources/organizations/policies.py b/src/gitpod/resources/organizations/policies.py
new file mode 100644
index 0000000..feb894c
--- /dev/null
+++ b/src/gitpod/resources/organizations/policies.py
@@ -0,0 +1,457 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.organizations import policy_update_params, policy_retrieve_params
+from ...types.organizations.policy_retrieve_response import PolicyRetrieveResponse
+
+__all__ = ["PoliciesResource", "AsyncPoliciesResource"]
+
+
+class PoliciesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> PoliciesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return PoliciesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> PoliciesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return PoliciesResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyRetrieveResponse:
+        """
+        Gets organization policy settings by organization ID.
+
+        Use this method to:
+
+        - Retrieve current policy settings for an organization
+        - View resource limits and restrictions
+        - Check allowed editors and other configurations
+
+        ### Examples
+
+        - Get organization policies:
+
+          Retrieves policy settings for a specific organization.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to retrieve policies for
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/GetOrganizationPolicies",
+            body=maybe_transform({"organization_id": organization_id}, policy_retrieve_params.PolicyRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        organization_id: str,
+        allowed_editor_ids: List[str] | NotGiven = NOT_GIVEN,
+        allow_local_runners: Optional[bool] | NotGiven = NOT_GIVEN,
+        default_editor_id: Optional[str] | NotGiven = NOT_GIVEN,
+        default_environment_image: Optional[str] | NotGiven = NOT_GIVEN,
+        maximum_environments_per_user: Optional[str] | NotGiven = NOT_GIVEN,
+        maximum_environment_timeout: Optional[str] | NotGiven = NOT_GIVEN,
+        maximum_running_environments_per_user: Optional[str] | NotGiven = NOT_GIVEN,
+        members_create_projects: Optional[bool] | NotGiven = NOT_GIVEN,
+        members_require_projects: Optional[bool] | NotGiven = NOT_GIVEN,
+        port_sharing_disabled: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates organization policy settings.
+
+        Use this method to:
+
+        - Configure editor restrictions
+        - Set environment resource limits
+        - Define project creation permissions
+        - Customize default configurations
+
+        ### Examples
+
+        - Update editor policies:
+
+          Restricts available editors and sets a default.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          allowedEditorIds:
+            - "vscode"
+            - "jetbrains"
+          defaultEditorId: "vscode"
+          ```
+
+        - Set environment limits:
+
+          Configures limits for environment usage.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          maximumEnvironmentTimeout: "3600s"
+          maximumRunningEnvironmentsPerUser: "5"
+          maximumEnvironmentsPerUser: "20"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to update policies for
+
+          allowed_editor_ids: allowed_editor_ids is the list of editor IDs that are allowed to be used in the
+              organization
+
+          allow_local_runners: allow_local_runners controls whether local runners are allowed to be used in the
+              organization
+
+          default_editor_id: default_editor_id is the default editor ID to be used when a user doesn't
+              specify one
+
+          default_environment_image: default_environment_image is the default container image when none is defined in
+              repo
+
+          maximum_environments_per_user: maximum_environments_per_user limits total environments (running or stopped) per
+              user
+
+          maximum_environment_timeout: maximum_environment_timeout controls the maximum timeout allowed for
+              environments in seconds. 0 means no limit (never). Minimum duration is 30
+              minutes.
+
+          maximum_running_environments_per_user: maximum_running_environments_per_user limits simultaneously running environments
+              per user
+
+          members_create_projects: members_create_projects controls whether members can create projects
+
+          members_require_projects: members_require_projects controls whether environments can only be created from
+              projects by non-admin users
+
+          port_sharing_disabled: port_sharing_disabled controls whether port sharing is disabled in the
+              organization
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/UpdateOrganizationPolicies",
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "allowed_editor_ids": allowed_editor_ids,
+                    "allow_local_runners": allow_local_runners,
+                    "default_editor_id": default_editor_id,
+                    "default_environment_image": default_environment_image,
+                    "maximum_environments_per_user": maximum_environments_per_user,
+                    "maximum_environment_timeout": maximum_environment_timeout,
+                    "maximum_running_environments_per_user": maximum_running_environments_per_user,
+                    "members_create_projects": members_create_projects,
+                    "members_require_projects": members_require_projects,
+                    "port_sharing_disabled": port_sharing_disabled,
+                },
+                policy_update_params.PolicyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncPoliciesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncPoliciesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncPoliciesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncPoliciesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncPoliciesResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyRetrieveResponse:
+        """
+        Gets organization policy settings by organization ID.
+
+        Use this method to:
+
+        - Retrieve current policy settings for an organization
+        - View resource limits and restrictions
+        - Check allowed editors and other configurations
+
+        ### Examples
+
+        - Get organization policies:
+
+          Retrieves policy settings for a specific organization.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to retrieve policies for
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/GetOrganizationPolicies",
+            body=await async_maybe_transform(
+                {"organization_id": organization_id}, policy_retrieve_params.PolicyRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        organization_id: str,
+        allowed_editor_ids: List[str] | NotGiven = NOT_GIVEN,
+        allow_local_runners: Optional[bool] | NotGiven = NOT_GIVEN,
+        default_editor_id: Optional[str] | NotGiven = NOT_GIVEN,
+        default_environment_image: Optional[str] | NotGiven = NOT_GIVEN,
+        maximum_environments_per_user: Optional[str] | NotGiven = NOT_GIVEN,
+        maximum_environment_timeout: Optional[str] | NotGiven = NOT_GIVEN,
+        maximum_running_environments_per_user: Optional[str] | NotGiven = NOT_GIVEN,
+        members_create_projects: Optional[bool] | NotGiven = NOT_GIVEN,
+        members_require_projects: Optional[bool] | NotGiven = NOT_GIVEN,
+        port_sharing_disabled: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates organization policy settings.
+
+        Use this method to:
+
+        - Configure editor restrictions
+        - Set environment resource limits
+        - Define project creation permissions
+        - Customize default configurations
+
+        ### Examples
+
+        - Update editor policies:
+
+          Restricts available editors and sets a default.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          allowedEditorIds:
+            - "vscode"
+            - "jetbrains"
+          defaultEditorId: "vscode"
+          ```
+
+        - Set environment limits:
+
+          Configures limits for environment usage.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          maximumEnvironmentTimeout: "3600s"
+          maximumRunningEnvironmentsPerUser: "5"
+          maximumEnvironmentsPerUser: "20"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to update policies for
+
+          allowed_editor_ids: allowed_editor_ids is the list of editor IDs that are allowed to be used in the
+              organization
+
+          allow_local_runners: allow_local_runners controls whether local runners are allowed to be used in the
+              organization
+
+          default_editor_id: default_editor_id is the default editor ID to be used when a user doesn't
+              specify one
+
+          default_environment_image: default_environment_image is the default container image when none is defined in
+              repo
+
+          maximum_environments_per_user: maximum_environments_per_user limits total environments (running or stopped) per
+              user
+
+          maximum_environment_timeout: maximum_environment_timeout controls the maximum timeout allowed for
+              environments in seconds. 0 means no limit (never). Minimum duration is 30
+              minutes.
+
+          maximum_running_environments_per_user: maximum_running_environments_per_user limits simultaneously running environments
+              per user
+
+          members_create_projects: members_create_projects controls whether members can create projects
+
+          members_require_projects: members_require_projects controls whether environments can only be created from
+              projects by non-admin users
+
+          port_sharing_disabled: port_sharing_disabled controls whether port sharing is disabled in the
+              organization
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/UpdateOrganizationPolicies",
+            body=await async_maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "allowed_editor_ids": allowed_editor_ids,
+                    "allow_local_runners": allow_local_runners,
+                    "default_editor_id": default_editor_id,
+                    "default_environment_image": default_environment_image,
+                    "maximum_environments_per_user": maximum_environments_per_user,
+                    "maximum_environment_timeout": maximum_environment_timeout,
+                    "maximum_running_environments_per_user": maximum_running_environments_per_user,
+                    "members_create_projects": members_create_projects,
+                    "members_require_projects": members_require_projects,
+                    "port_sharing_disabled": port_sharing_disabled,
+                },
+                policy_update_params.PolicyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class PoliciesResourceWithRawResponse:
+    def __init__(self, policies: PoliciesResource) -> None:
+        self._policies = policies
+
+        self.retrieve = to_raw_response_wrapper(
+            policies.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            policies.update,
+        )
+
+
+class AsyncPoliciesResourceWithRawResponse:
+    def __init__(self, policies: AsyncPoliciesResource) -> None:
+        self._policies = policies
+
+        self.retrieve = async_to_raw_response_wrapper(
+            policies.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            policies.update,
+        )
+
+
+class PoliciesResourceWithStreamingResponse:
+    def __init__(self, policies: PoliciesResource) -> None:
+        self._policies = policies
+
+        self.retrieve = to_streamed_response_wrapper(
+            policies.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            policies.update,
+        )
+
+
+class AsyncPoliciesResourceWithStreamingResponse:
+    def __init__(self, policies: AsyncPoliciesResource) -> None:
+        self._policies = policies
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            policies.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            policies.update,
+        )
diff --git a/src/gitpod/resources/organizations/sso_configurations.py b/src/gitpod/resources/organizations/sso_configurations.py
new file mode 100644
index 0000000..6cdb528
--- /dev/null
+++ b/src/gitpod/resources/organizations/sso_configurations.py
@@ -0,0 +1,890 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncSSOConfigurationsPage, AsyncSSOConfigurationsPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.organizations import (
+    SSOConfigurationState,
+    sso_configuration_list_params,
+    sso_configuration_create_params,
+    sso_configuration_delete_params,
+    sso_configuration_update_params,
+    sso_configuration_retrieve_params,
+)
+from ...types.organizations.sso_configuration import SSOConfiguration
+from ...types.organizations.sso_configuration_state import SSOConfigurationState
+from ...types.organizations.sso_configuration_create_response import SSOConfigurationCreateResponse
+from ...types.organizations.sso_configuration_retrieve_response import SSOConfigurationRetrieveResponse
+
+__all__ = ["SSOConfigurationsResource", "AsyncSSOConfigurationsResource"]
+
+
+class SSOConfigurationsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> SSOConfigurationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return SSOConfigurationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> SSOConfigurationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return SSOConfigurationsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        client_id: str,
+        client_secret: str,
+        email_domain: str,
+        issuer_url: str,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SSOConfigurationCreateResponse:
+        """
+        Creates or updates SSO configuration for organizational authentication.
+
+        Use this method to:
+
+        - Configure OIDC-based SSO providers
+        - Set up built-in providers (Google, GitHub, etc.)
+        - Define custom identity providers
+        - Manage authentication policies
+
+        ### Examples
+
+        - Configure built-in Google SSO:
+
+          Sets up SSO using Google Workspace.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          clientId: "012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com"
+          clientSecret: "GOCSPX-abcdefghijklmnopqrstuvwxyz123456"
+          issuerUrl: "https://accounts.google.com"
+          emailDomain: "acme-corp.com"
+          ```
+
+        - Configure custom OIDC provider:
+
+          Sets up SSO with a custom identity provider.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          clientId: "acme-corp-gitpod"
+          clientSecret: "secret-token-value"
+          issuerUrl: "https://sso.acme-corp.com"
+          emailDomain: "acme-corp.com"
+          ```
+
+        Args:
+          client_id: client_id is the client ID of the OIDC application set on the IdP
+
+          client_secret: client_secret is the client secret of the OIDC application set on the IdP
+
+          email_domain: email_domain is the domain that is allowed to sign in to the organization
+
+          issuer_url: issuer_url is the URL of the IdP issuer
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/CreateSSOConfiguration",
+            body=maybe_transform(
+                {
+                    "client_id": client_id,
+                    "client_secret": client_secret,
+                    "email_domain": email_domain,
+                    "issuer_url": issuer_url,
+                    "organization_id": organization_id,
+                },
+                sso_configuration_create_params.SSOConfigurationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SSOConfigurationCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        sso_configuration_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SSOConfigurationRetrieveResponse:
+        """
+        Retrieves a specific SSO configuration.
+
+        Use this method to:
+
+        - View SSO provider details
+        - Check configuration status
+        - Verify SSO settings
+
+        ### Examples
+
+        - Get SSO configuration:
+
+          Retrieves details of a specific SSO configuration.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          sso_configuration_id: sso_configuration_id is the ID of the SSO configuration to get
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/GetSSOConfiguration",
+            body=maybe_transform(
+                {"sso_configuration_id": sso_configuration_id},
+                sso_configuration_retrieve_params.SSOConfigurationRetrieveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SSOConfigurationRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        sso_configuration_id: str,
+        claims: Dict[str, str] | NotGiven = NOT_GIVEN,
+        client_id: Optional[str] | NotGiven = NOT_GIVEN,
+        client_secret: Optional[str] | NotGiven = NOT_GIVEN,
+        email_domain: Optional[str] | NotGiven = NOT_GIVEN,
+        issuer_url: Optional[str] | NotGiven = NOT_GIVEN,
+        state: Optional[SSOConfigurationState] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates SSO provider settings and authentication rules.
+
+        Use this method to:
+
+        - Rotate client credentials
+        - Update provider endpoints
+        - Modify claim mappings
+        - Change authentication policies
+        - Toggle SSO enforcement
+
+        ### Examples
+
+        - Update credentials:
+
+          Rotates client ID and secret.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          clientId: "new-client-id"
+          clientSecret: "new-client-secret"
+          ```
+
+        - Update provider status:
+
+          Activates or deactivates SSO provider.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          state: SSO_CONFIGURATION_STATE_ACTIVE
+          ```
+
+        Args:
+          sso_configuration_id: sso_configuration_id is the ID of the SSO configuration to update
+
+          claims: claims are key/value pairs that defines a mapping of claims issued by the IdP.
+
+          client_id: client_id is the client ID of the SSO provider
+
+          client_secret: client_secret is the client secret of the SSO provider
+
+          issuer_url: issuer_url is the URL of the IdP issuer
+
+          state: state is the state of the SSO configuration
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/UpdateSSOConfiguration",
+            body=maybe_transform(
+                {
+                    "sso_configuration_id": sso_configuration_id,
+                    "claims": claims,
+                    "client_id": client_id,
+                    "client_secret": client_secret,
+                    "email_domain": email_domain,
+                    "issuer_url": issuer_url,
+                    "state": state,
+                },
+                sso_configuration_update_params.SSOConfigurationUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        organization_id: str,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: sso_configuration_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncSSOConfigurationsPage[SSOConfiguration]:
+        """
+        Lists and filters SSO configurations for an organization.
+
+        Use this method to:
+
+        - View all SSO providers
+        - Monitor authentication status
+        - Audit security settings
+        - Manage provider configurations
+
+        ### Examples
+
+        - List active configurations:
+
+          Shows all active SSO providers.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List by provider type:
+
+          Shows custom SSO configurations.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to list SSO configurations for.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.OrganizationService/ListSSOConfigurations",
+            page=SyncSSOConfigurationsPage[SSOConfiguration],
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "pagination": pagination,
+                },
+                sso_configuration_list_params.SSOConfigurationListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    sso_configuration_list_params.SSOConfigurationListParams,
+                ),
+            ),
+            model=SSOConfiguration,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        sso_configuration_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Removes an SSO configuration from an organization.
+
+        Use this method to:
+
+        - Disable SSO authentication
+        - Remove outdated providers
+        - Clean up unused configurations
+
+        ### Examples
+
+        - Delete SSO configuration:
+
+          Removes a specific SSO configuration.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.OrganizationService/DeleteSSOConfiguration",
+            body=maybe_transform(
+                {"sso_configuration_id": sso_configuration_id},
+                sso_configuration_delete_params.SSOConfigurationDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncSSOConfigurationsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncSSOConfigurationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncSSOConfigurationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncSSOConfigurationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncSSOConfigurationsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        client_id: str,
+        client_secret: str,
+        email_domain: str,
+        issuer_url: str,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SSOConfigurationCreateResponse:
+        """
+        Creates or updates SSO configuration for organizational authentication.
+
+        Use this method to:
+
+        - Configure OIDC-based SSO providers
+        - Set up built-in providers (Google, GitHub, etc.)
+        - Define custom identity providers
+        - Manage authentication policies
+
+        ### Examples
+
+        - Configure built-in Google SSO:
+
+          Sets up SSO using Google Workspace.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          clientId: "012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com"
+          clientSecret: "GOCSPX-abcdefghijklmnopqrstuvwxyz123456"
+          issuerUrl: "https://accounts.google.com"
+          emailDomain: "acme-corp.com"
+          ```
+
+        - Configure custom OIDC provider:
+
+          Sets up SSO with a custom identity provider.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          clientId: "acme-corp-gitpod"
+          clientSecret: "secret-token-value"
+          issuerUrl: "https://sso.acme-corp.com"
+          emailDomain: "acme-corp.com"
+          ```
+
+        Args:
+          client_id: client_id is the client ID of the OIDC application set on the IdP
+
+          client_secret: client_secret is the client secret of the OIDC application set on the IdP
+
+          email_domain: email_domain is the domain that is allowed to sign in to the organization
+
+          issuer_url: issuer_url is the URL of the IdP issuer
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/CreateSSOConfiguration",
+            body=await async_maybe_transform(
+                {
+                    "client_id": client_id,
+                    "client_secret": client_secret,
+                    "email_domain": email_domain,
+                    "issuer_url": issuer_url,
+                    "organization_id": organization_id,
+                },
+                sso_configuration_create_params.SSOConfigurationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SSOConfigurationCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        sso_configuration_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SSOConfigurationRetrieveResponse:
+        """
+        Retrieves a specific SSO configuration.
+
+        Use this method to:
+
+        - View SSO provider details
+        - Check configuration status
+        - Verify SSO settings
+
+        ### Examples
+
+        - Get SSO configuration:
+
+          Retrieves details of a specific SSO configuration.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          sso_configuration_id: sso_configuration_id is the ID of the SSO configuration to get
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/GetSSOConfiguration",
+            body=await async_maybe_transform(
+                {"sso_configuration_id": sso_configuration_id},
+                sso_configuration_retrieve_params.SSOConfigurationRetrieveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SSOConfigurationRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        sso_configuration_id: str,
+        claims: Dict[str, str] | NotGiven = NOT_GIVEN,
+        client_id: Optional[str] | NotGiven = NOT_GIVEN,
+        client_secret: Optional[str] | NotGiven = NOT_GIVEN,
+        email_domain: Optional[str] | NotGiven = NOT_GIVEN,
+        issuer_url: Optional[str] | NotGiven = NOT_GIVEN,
+        state: Optional[SSOConfigurationState] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates SSO provider settings and authentication rules.
+
+        Use this method to:
+
+        - Rotate client credentials
+        - Update provider endpoints
+        - Modify claim mappings
+        - Change authentication policies
+        - Toggle SSO enforcement
+
+        ### Examples
+
+        - Update credentials:
+
+          Rotates client ID and secret.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          clientId: "new-client-id"
+          clientSecret: "new-client-secret"
+          ```
+
+        - Update provider status:
+
+          Activates or deactivates SSO provider.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          state: SSO_CONFIGURATION_STATE_ACTIVE
+          ```
+
+        Args:
+          sso_configuration_id: sso_configuration_id is the ID of the SSO configuration to update
+
+          claims: claims are key/value pairs that defines a mapping of claims issued by the IdP.
+
+          client_id: client_id is the client ID of the SSO provider
+
+          client_secret: client_secret is the client secret of the SSO provider
+
+          issuer_url: issuer_url is the URL of the IdP issuer
+
+          state: state is the state of the SSO configuration
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/UpdateSSOConfiguration",
+            body=await async_maybe_transform(
+                {
+                    "sso_configuration_id": sso_configuration_id,
+                    "claims": claims,
+                    "client_id": client_id,
+                    "client_secret": client_secret,
+                    "email_domain": email_domain,
+                    "issuer_url": issuer_url,
+                    "state": state,
+                },
+                sso_configuration_update_params.SSOConfigurationUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        organization_id: str,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: sso_configuration_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[SSOConfiguration, AsyncSSOConfigurationsPage[SSOConfiguration]]:
+        """
+        Lists and filters SSO configurations for an organization.
+
+        Use this method to:
+
+        - View all SSO providers
+        - Monitor authentication status
+        - Audit security settings
+        - Manage provider configurations
+
+        ### Examples
+
+        - List active configurations:
+
+          Shows all active SSO providers.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List by provider type:
+
+          Shows custom SSO configurations.
+
+          ```yaml
+          organizationId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+            token: "next-page-token-from-previous-response"
+          ```
+
+        Args:
+          organization_id: organization_id is the ID of the organization to list SSO configurations for.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.OrganizationService/ListSSOConfigurations",
+            page=AsyncSSOConfigurationsPage[SSOConfiguration],
+            body=maybe_transform(
+                {
+                    "organization_id": organization_id,
+                    "pagination": pagination,
+                },
+                sso_configuration_list_params.SSOConfigurationListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    sso_configuration_list_params.SSOConfigurationListParams,
+                ),
+            ),
+            model=SSOConfiguration,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        sso_configuration_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Removes an SSO configuration from an organization.
+
+        Use this method to:
+
+        - Disable SSO authentication
+        - Remove outdated providers
+        - Clean up unused configurations
+
+        ### Examples
+
+        - Delete SSO configuration:
+
+          Removes a specific SSO configuration.
+
+          ```yaml
+          ssoConfigurationId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.OrganizationService/DeleteSSOConfiguration",
+            body=await async_maybe_transform(
+                {"sso_configuration_id": sso_configuration_id},
+                sso_configuration_delete_params.SSOConfigurationDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class SSOConfigurationsResourceWithRawResponse:
+    def __init__(self, sso_configurations: SSOConfigurationsResource) -> None:
+        self._sso_configurations = sso_configurations
+
+        self.create = to_raw_response_wrapper(
+            sso_configurations.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            sso_configurations.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            sso_configurations.update,
+        )
+        self.list = to_raw_response_wrapper(
+            sso_configurations.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            sso_configurations.delete,
+        )
+
+
+class AsyncSSOConfigurationsResourceWithRawResponse:
+    def __init__(self, sso_configurations: AsyncSSOConfigurationsResource) -> None:
+        self._sso_configurations = sso_configurations
+
+        self.create = async_to_raw_response_wrapper(
+            sso_configurations.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            sso_configurations.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            sso_configurations.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            sso_configurations.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            sso_configurations.delete,
+        )
+
+
+class SSOConfigurationsResourceWithStreamingResponse:
+    def __init__(self, sso_configurations: SSOConfigurationsResource) -> None:
+        self._sso_configurations = sso_configurations
+
+        self.create = to_streamed_response_wrapper(
+            sso_configurations.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            sso_configurations.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            sso_configurations.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            sso_configurations.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            sso_configurations.delete,
+        )
+
+
+class AsyncSSOConfigurationsResourceWithStreamingResponse:
+    def __init__(self, sso_configurations: AsyncSSOConfigurationsResource) -> None:
+        self._sso_configurations = sso_configurations
+
+        self.create = async_to_streamed_response_wrapper(
+            sso_configurations.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            sso_configurations.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            sso_configurations.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            sso_configurations.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            sso_configurations.delete,
+        )
diff --git a/src/gitpod/resources/projects/__init__.py b/src/gitpod/resources/projects/__init__.py
new file mode 100644
index 0000000..da8b964
--- /dev/null
+++ b/src/gitpod/resources/projects/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .policies import (
+    PoliciesResource,
+    AsyncPoliciesResource,
+    PoliciesResourceWithRawResponse,
+    AsyncPoliciesResourceWithRawResponse,
+    PoliciesResourceWithStreamingResponse,
+    AsyncPoliciesResourceWithStreamingResponse,
+)
+from .projects import (
+    ProjectsResource,
+    AsyncProjectsResource,
+    ProjectsResourceWithRawResponse,
+    AsyncProjectsResourceWithRawResponse,
+    ProjectsResourceWithStreamingResponse,
+    AsyncProjectsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "PoliciesResource",
+    "AsyncPoliciesResource",
+    "PoliciesResourceWithRawResponse",
+    "AsyncPoliciesResourceWithRawResponse",
+    "PoliciesResourceWithStreamingResponse",
+    "AsyncPoliciesResourceWithStreamingResponse",
+    "ProjectsResource",
+    "AsyncProjectsResource",
+    "ProjectsResourceWithRawResponse",
+    "AsyncProjectsResourceWithRawResponse",
+    "ProjectsResourceWithStreamingResponse",
+    "AsyncProjectsResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/projects/policies.py b/src/gitpod/resources/projects/policies.py
new file mode 100644
index 0000000..fc12621
--- /dev/null
+++ b/src/gitpod/resources/projects/policies.py
@@ -0,0 +1,667 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncPoliciesPage, AsyncPoliciesPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.projects import (
+    ProjectRole,
+    policy_list_params,
+    policy_create_params,
+    policy_delete_params,
+    policy_update_params,
+)
+from ...types.projects.project_role import ProjectRole
+from ...types.projects.project_policy import ProjectPolicy
+from ...types.projects.policy_create_response import PolicyCreateResponse
+from ...types.projects.policy_update_response import PolicyUpdateResponse
+
+__all__ = ["PoliciesResource", "AsyncPoliciesResource"]
+
+
+class PoliciesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> PoliciesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return PoliciesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> PoliciesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return PoliciesResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        role: ProjectRole | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyCreateResponse:
+        """
+        Creates a new policy for a project.
+
+        Use this method to:
+
+        - Set up access controls
+        - Define group permissions
+        - Configure role-based access
+
+        ### Examples
+
+        - Create admin policy:
+
+          Grants admin access to a group.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: PROJECT_ROLE_ADMIN
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/CreateProjectPolicy",
+            body=maybe_transform(
+                {
+                    "group_id": group_id,
+                    "project_id": project_id,
+                    "role": role,
+                },
+                policy_create_params.PolicyCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyCreateResponse,
+        )
+
+    def update(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        role: ProjectRole | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyUpdateResponse:
+        """
+        Updates an existing project policy.
+
+        Use this method to:
+
+        - Modify access levels
+        - Change group roles
+        - Update permissions
+
+        ### Examples
+
+        - Update policy role:
+
+          Changes a group's access level.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: PROJECT_ROLE_EDITOR
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/UpdateProjectPolicy",
+            body=maybe_transform(
+                {
+                    "group_id": group_id,
+                    "project_id": project_id,
+                    "role": role,
+                },
+                policy_update_params.PolicyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyUpdateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: policy_list_params.Pagination | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncPoliciesPage[ProjectPolicy]:
+        """
+        Lists policies for a project.
+
+        Use this method to:
+
+        - View access controls
+        - Check policy configurations
+        - Audit permissions
+
+        ### Examples
+
+        - List policies:
+
+          Shows all policies for a project.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing project policies
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.ProjectService/ListProjectPolicies",
+            page=SyncPoliciesPage[ProjectPolicy],
+            body=maybe_transform(
+                {
+                    "pagination": pagination,
+                    "project_id": project_id,
+                },
+                policy_list_params.PolicyListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    policy_list_params.PolicyListParams,
+                ),
+            ),
+            model=ProjectPolicy,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a project policy.
+
+        Use this method to:
+
+        - Remove access controls
+        - Revoke permissions
+        - Clean up policies
+
+        ### Examples
+
+        - Delete policy:
+
+          Removes a group's access policy.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/DeleteProjectPolicy",
+            body=maybe_transform(
+                {
+                    "group_id": group_id,
+                    "project_id": project_id,
+                },
+                policy_delete_params.PolicyDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncPoliciesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncPoliciesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncPoliciesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncPoliciesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncPoliciesResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        role: ProjectRole | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyCreateResponse:
+        """
+        Creates a new policy for a project.
+
+        Use this method to:
+
+        - Set up access controls
+        - Define group permissions
+        - Configure role-based access
+
+        ### Examples
+
+        - Create admin policy:
+
+          Grants admin access to a group.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: PROJECT_ROLE_ADMIN
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/CreateProjectPolicy",
+            body=await async_maybe_transform(
+                {
+                    "group_id": group_id,
+                    "project_id": project_id,
+                    "role": role,
+                },
+                policy_create_params.PolicyCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyCreateResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        role: ProjectRole | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyUpdateResponse:
+        """
+        Updates an existing project policy.
+
+        Use this method to:
+
+        - Modify access levels
+        - Change group roles
+        - Update permissions
+
+        ### Examples
+
+        - Update policy role:
+
+          Changes a group's access level.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: PROJECT_ROLE_EDITOR
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/UpdateProjectPolicy",
+            body=await async_maybe_transform(
+                {
+                    "group_id": group_id,
+                    "project_id": project_id,
+                    "role": role,
+                },
+                policy_update_params.PolicyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyUpdateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: policy_list_params.Pagination | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[ProjectPolicy, AsyncPoliciesPage[ProjectPolicy]]:
+        """
+        Lists policies for a project.
+
+        Use this method to:
+
+        - View access controls
+        - Check policy configurations
+        - Audit permissions
+
+        ### Examples
+
+        - List policies:
+
+          Shows all policies for a project.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing project policies
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.ProjectService/ListProjectPolicies",
+            page=AsyncPoliciesPage[ProjectPolicy],
+            body=maybe_transform(
+                {
+                    "pagination": pagination,
+                    "project_id": project_id,
+                },
+                policy_list_params.PolicyListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    policy_list_params.PolicyListParams,
+                ),
+            ),
+            model=ProjectPolicy,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a project policy.
+
+        Use this method to:
+
+        - Remove access controls
+        - Revoke permissions
+        - Clean up policies
+
+        ### Examples
+
+        - Delete policy:
+
+          Removes a group's access policy.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/DeleteProjectPolicy",
+            body=await async_maybe_transform(
+                {
+                    "group_id": group_id,
+                    "project_id": project_id,
+                },
+                policy_delete_params.PolicyDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class PoliciesResourceWithRawResponse:
+    def __init__(self, policies: PoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = to_raw_response_wrapper(
+            policies.create,
+        )
+        self.update = to_raw_response_wrapper(
+            policies.update,
+        )
+        self.list = to_raw_response_wrapper(
+            policies.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            policies.delete,
+        )
+
+
+class AsyncPoliciesResourceWithRawResponse:
+    def __init__(self, policies: AsyncPoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = async_to_raw_response_wrapper(
+            policies.create,
+        )
+        self.update = async_to_raw_response_wrapper(
+            policies.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            policies.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            policies.delete,
+        )
+
+
+class PoliciesResourceWithStreamingResponse:
+    def __init__(self, policies: PoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = to_streamed_response_wrapper(
+            policies.create,
+        )
+        self.update = to_streamed_response_wrapper(
+            policies.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            policies.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            policies.delete,
+        )
+
+
+class AsyncPoliciesResourceWithStreamingResponse:
+    def __init__(self, policies: AsyncPoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = async_to_streamed_response_wrapper(
+            policies.create,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            policies.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            policies.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            policies.delete,
+        )
diff --git a/src/gitpod/resources/projects/projects.py b/src/gitpod/resources/projects/projects.py
new file mode 100644
index 0000000..2c8e637
--- /dev/null
+++ b/src/gitpod/resources/projects/projects.py
@@ -0,0 +1,1073 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...types import (
+    project_list_params,
+    project_create_params,
+    project_delete_params,
+    project_update_params,
+    project_retrieve_params,
+    project_create_from_environment_params,
+)
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from .policies import (
+    PoliciesResource,
+    AsyncPoliciesResource,
+    PoliciesResourceWithRawResponse,
+    AsyncPoliciesResourceWithRawResponse,
+    PoliciesResourceWithStreamingResponse,
+    AsyncPoliciesResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncProjectsPage, AsyncProjectsPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.project import Project
+from ...types.project_create_response import ProjectCreateResponse
+from ...types.project_update_response import ProjectUpdateResponse
+from ...types.project_retrieve_response import ProjectRetrieveResponse
+from ...types.environment_initializer_param import EnvironmentInitializerParam
+from ...types.project_environment_class_param import ProjectEnvironmentClassParam
+from ...types.project_create_from_environment_response import ProjectCreateFromEnvironmentResponse
+
+__all__ = ["ProjectsResource", "AsyncProjectsResource"]
+
+
+class ProjectsResource(SyncAPIResource):
+    @cached_property
+    def policies(self) -> PoliciesResource:
+        return PoliciesResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> ProjectsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return ProjectsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ProjectsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return ProjectsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        environment_class: ProjectEnvironmentClassParam,
+        initializer: EnvironmentInitializerParam,
+        automations_file_path: str | NotGiven = NOT_GIVEN,
+        devcontainer_file_path: str | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        technical_description: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectCreateResponse:
+        """
+        Creates a new project with specified configuration.
+
+        Use this method to:
+
+        - Set up development projects
+        - Configure project environments
+        - Define project settings
+        - Initialize project content
+
+        ### Examples
+
+        - Create basic project:
+
+          Creates a project with minimal configuration.
+
+          ```yaml
+          name: "Web Application"
+          environmentClass:
+            environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          initializer:
+            specs:
+              - git:
+                  remoteUri: "https://github.com/org/repo"
+          ```
+
+        - Create project with devcontainer:
+
+          Creates a project with custom development container.
+
+          ```yaml
+          name: "Backend Service"
+          environmentClass:
+            environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          initializer:
+            specs:
+              - git:
+                  remoteUri: "https://github.com/org/backend"
+          devcontainerFilePath: ".devcontainer/devcontainer.json"
+          automationsFilePath: ".gitpod/automations.yaml"
+          ```
+
+        Args:
+          initializer: initializer is the content initializer
+
+          automations_file_path: automations_file_path is the path to the automations file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          devcontainer_file_path: devcontainer_file_path is the path to the devcontainer file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          technical_description: technical_description is a detailed technical description of the project This
+              field is not returned by default in GetProject or ListProjects responses 8KB max
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/CreateProject",
+            body=maybe_transform(
+                {
+                    "environment_class": environment_class,
+                    "initializer": initializer,
+                    "automations_file_path": automations_file_path,
+                    "devcontainer_file_path": devcontainer_file_path,
+                    "name": name,
+                    "technical_description": technical_description,
+                },
+                project_create_params.ProjectCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectRetrieveResponse:
+        """
+        Gets details about a specific project.
+
+        Use this method to:
+
+        - View project configuration
+        - Check project status
+        - Get project metadata
+
+        ### Examples
+
+        - Get project details:
+
+          Retrieves information about a specific project.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/GetProject",
+            body=maybe_transform({"project_id": project_id}, project_retrieve_params.ProjectRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        automations_file_path: Optional[str] | NotGiven = NOT_GIVEN,
+        devcontainer_file_path: Optional[str] | NotGiven = NOT_GIVEN,
+        environment_class: Optional[ProjectEnvironmentClassParam] | NotGiven = NOT_GIVEN,
+        initializer: Optional[EnvironmentInitializerParam] | NotGiven = NOT_GIVEN,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        technical_description: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectUpdateResponse:
+        """
+        Updates a project's configuration.
+
+        Use this method to:
+
+        - Modify project settings
+        - Update environment class
+        - Change project name
+        - Configure initializers
+
+        ### Examples
+
+        - Update project name:
+
+          Changes the project's display name.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          name: "New Project Name"
+          ```
+
+        - Update environment class:
+
+          Changes the project's environment class.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          environmentClass:
+            environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          automations_file_path: automations_file_path is the path to the automations file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          devcontainer_file_path: devcontainer_file_path is the path to the devcontainer file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          initializer: initializer is the content initializer
+
+          project_id: project_id specifies the project identifier
+
+          technical_description: technical_description is a detailed technical description of the project This
+              field is not returned by default in GetProject or ListProjects responses 8KB max
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/UpdateProject",
+            body=maybe_transform(
+                {
+                    "automations_file_path": automations_file_path,
+                    "devcontainer_file_path": devcontainer_file_path,
+                    "environment_class": environment_class,
+                    "initializer": initializer,
+                    "name": name,
+                    "project_id": project_id,
+                    "technical_description": technical_description,
+                },
+                project_update_params.ProjectUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectUpdateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: project_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: project_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncProjectsPage[Project]:
+        """
+        Lists projects with optional filtering.
+
+        Use this method to:
+
+        - View all accessible projects
+        - Browse project configurations
+        - Monitor project status
+
+        ### Examples
+
+        - List projects:
+
+          Shows all projects with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing organizations
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.ProjectService/ListProjects",
+            page=SyncProjectsPage[Project],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                project_list_params.ProjectListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    project_list_params.ProjectListParams,
+                ),
+            ),
+            model=Project,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a project permanently.
+
+        Use this method to:
+
+        - Remove unused projects
+        - Clean up test projects
+        - Delete obsolete configurations
+
+        ### Examples
+
+        - Delete project:
+
+          Permanently removes a project.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/DeleteProject",
+            body=maybe_transform({"project_id": project_id}, project_delete_params.ProjectDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def create_from_environment(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectCreateFromEnvironmentResponse:
+        """
+        Creates a new project using an existing environment as a template.
+
+        Use this method to:
+
+        - Clone environment configurations
+        - Create projects from templates
+        - Share environment setups
+
+        ### Examples
+
+        - Create from environment:
+
+          Creates a project based on existing environment.
+
+          ```yaml
+          name: "Frontend Project"
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.ProjectService/CreateProjectFromEnvironment",
+            body=maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "name": name,
+                },
+                project_create_from_environment_params.ProjectCreateFromEnvironmentParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectCreateFromEnvironmentResponse,
+        )
+
+
+class AsyncProjectsResource(AsyncAPIResource):
+    @cached_property
+    def policies(self) -> AsyncPoliciesResource:
+        return AsyncPoliciesResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncProjectsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncProjectsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncProjectsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        environment_class: ProjectEnvironmentClassParam,
+        initializer: EnvironmentInitializerParam,
+        automations_file_path: str | NotGiven = NOT_GIVEN,
+        devcontainer_file_path: str | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        technical_description: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectCreateResponse:
+        """
+        Creates a new project with specified configuration.
+
+        Use this method to:
+
+        - Set up development projects
+        - Configure project environments
+        - Define project settings
+        - Initialize project content
+
+        ### Examples
+
+        - Create basic project:
+
+          Creates a project with minimal configuration.
+
+          ```yaml
+          name: "Web Application"
+          environmentClass:
+            environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          initializer:
+            specs:
+              - git:
+                  remoteUri: "https://github.com/org/repo"
+          ```
+
+        - Create project with devcontainer:
+
+          Creates a project with custom development container.
+
+          ```yaml
+          name: "Backend Service"
+          environmentClass:
+            environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          initializer:
+            specs:
+              - git:
+                  remoteUri: "https://github.com/org/backend"
+          devcontainerFilePath: ".devcontainer/devcontainer.json"
+          automationsFilePath: ".gitpod/automations.yaml"
+          ```
+
+        Args:
+          initializer: initializer is the content initializer
+
+          automations_file_path: automations_file_path is the path to the automations file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          devcontainer_file_path: devcontainer_file_path is the path to the devcontainer file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          technical_description: technical_description is a detailed technical description of the project This
+              field is not returned by default in GetProject or ListProjects responses 8KB max
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/CreateProject",
+            body=await async_maybe_transform(
+                {
+                    "environment_class": environment_class,
+                    "initializer": initializer,
+                    "automations_file_path": automations_file_path,
+                    "devcontainer_file_path": devcontainer_file_path,
+                    "name": name,
+                    "technical_description": technical_description,
+                },
+                project_create_params.ProjectCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectRetrieveResponse:
+        """
+        Gets details about a specific project.
+
+        Use this method to:
+
+        - View project configuration
+        - Check project status
+        - Get project metadata
+
+        ### Examples
+
+        - Get project details:
+
+          Retrieves information about a specific project.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/GetProject",
+            body=await async_maybe_transform({"project_id": project_id}, project_retrieve_params.ProjectRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        automations_file_path: Optional[str] | NotGiven = NOT_GIVEN,
+        devcontainer_file_path: Optional[str] | NotGiven = NOT_GIVEN,
+        environment_class: Optional[ProjectEnvironmentClassParam] | NotGiven = NOT_GIVEN,
+        initializer: Optional[EnvironmentInitializerParam] | NotGiven = NOT_GIVEN,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        technical_description: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectUpdateResponse:
+        """
+        Updates a project's configuration.
+
+        Use this method to:
+
+        - Modify project settings
+        - Update environment class
+        - Change project name
+        - Configure initializers
+
+        ### Examples
+
+        - Update project name:
+
+          Changes the project's display name.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          name: "New Project Name"
+          ```
+
+        - Update environment class:
+
+          Changes the project's environment class.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          environmentClass:
+            environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          automations_file_path: automations_file_path is the path to the automations file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          devcontainer_file_path: devcontainer_file_path is the path to the devcontainer file relative to the repo
+              root path must not be absolute (start with a /):
+
+              ```
+              this.matches("^$|^[^/].*")
+              ```
+
+          initializer: initializer is the content initializer
+
+          project_id: project_id specifies the project identifier
+
+          technical_description: technical_description is a detailed technical description of the project This
+              field is not returned by default in GetProject or ListProjects responses 8KB max
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/UpdateProject",
+            body=await async_maybe_transform(
+                {
+                    "automations_file_path": automations_file_path,
+                    "devcontainer_file_path": devcontainer_file_path,
+                    "environment_class": environment_class,
+                    "initializer": initializer,
+                    "name": name,
+                    "project_id": project_id,
+                    "technical_description": technical_description,
+                },
+                project_update_params.ProjectUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectUpdateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: project_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: project_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Project, AsyncProjectsPage[Project]]:
+        """
+        Lists projects with optional filtering.
+
+        Use this method to:
+
+        - View all accessible projects
+        - Browse project configurations
+        - Monitor project status
+
+        ### Examples
+
+        - List projects:
+
+          Shows all projects with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing organizations
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.ProjectService/ListProjects",
+            page=AsyncProjectsPage[Project],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                project_list_params.ProjectListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    project_list_params.ProjectListParams,
+                ),
+            ),
+            model=Project,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        project_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a project permanently.
+
+        Use this method to:
+
+        - Remove unused projects
+        - Clean up test projects
+        - Delete obsolete configurations
+
+        ### Examples
+
+        - Delete project:
+
+          Permanently removes a project.
+
+          ```yaml
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          ```
+
+        Args:
+          project_id: project_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/DeleteProject",
+            body=await async_maybe_transform({"project_id": project_id}, project_delete_params.ProjectDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def create_from_environment(
+        self,
+        *,
+        environment_id: str | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectCreateFromEnvironmentResponse:
+        """
+        Creates a new project using an existing environment as a template.
+
+        Use this method to:
+
+        - Clone environment configurations
+        - Create projects from templates
+        - Share environment setups
+
+        ### Examples
+
+        - Create from environment:
+
+          Creates a project based on existing environment.
+
+          ```yaml
+          name: "Frontend Project"
+          environmentId: "07e03a28-65a5-4d98-b532-8ea67b188048"
+          ```
+
+        Args:
+          environment_id: environment_id specifies the environment identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.ProjectService/CreateProjectFromEnvironment",
+            body=await async_maybe_transform(
+                {
+                    "environment_id": environment_id,
+                    "name": name,
+                },
+                project_create_from_environment_params.ProjectCreateFromEnvironmentParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectCreateFromEnvironmentResponse,
+        )
+
+
+class ProjectsResourceWithRawResponse:
+    def __init__(self, projects: ProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = to_raw_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            projects.update,
+        )
+        self.list = to_raw_response_wrapper(
+            projects.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            projects.delete,
+        )
+        self.create_from_environment = to_raw_response_wrapper(
+            projects.create_from_environment,
+        )
+
+    @cached_property
+    def policies(self) -> PoliciesResourceWithRawResponse:
+        return PoliciesResourceWithRawResponse(self._projects.policies)
+
+
+class AsyncProjectsResourceWithRawResponse:
+    def __init__(self, projects: AsyncProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = async_to_raw_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            projects.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            projects.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            projects.delete,
+        )
+        self.create_from_environment = async_to_raw_response_wrapper(
+            projects.create_from_environment,
+        )
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResourceWithRawResponse:
+        return AsyncPoliciesResourceWithRawResponse(self._projects.policies)
+
+
+class ProjectsResourceWithStreamingResponse:
+    def __init__(self, projects: ProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = to_streamed_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            projects.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            projects.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            projects.delete,
+        )
+        self.create_from_environment = to_streamed_response_wrapper(
+            projects.create_from_environment,
+        )
+
+    @cached_property
+    def policies(self) -> PoliciesResourceWithStreamingResponse:
+        return PoliciesResourceWithStreamingResponse(self._projects.policies)
+
+
+class AsyncProjectsResourceWithStreamingResponse:
+    def __init__(self, projects: AsyncProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = async_to_streamed_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            projects.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            projects.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            projects.delete,
+        )
+        self.create_from_environment = async_to_streamed_response_wrapper(
+            projects.create_from_environment,
+        )
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResourceWithStreamingResponse:
+        return AsyncPoliciesResourceWithStreamingResponse(self._projects.policies)
diff --git a/src/gitpod/resources/runners/__init__.py b/src/gitpod/resources/runners/__init__.py
new file mode 100644
index 0000000..27ce34c
--- /dev/null
+++ b/src/gitpod/resources/runners/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .runners import (
+    RunnersResource,
+    AsyncRunnersResource,
+    RunnersResourceWithRawResponse,
+    AsyncRunnersResourceWithRawResponse,
+    RunnersResourceWithStreamingResponse,
+    AsyncRunnersResourceWithStreamingResponse,
+)
+from .policies import (
+    PoliciesResource,
+    AsyncPoliciesResource,
+    PoliciesResourceWithRawResponse,
+    AsyncPoliciesResourceWithRawResponse,
+    PoliciesResourceWithStreamingResponse,
+    AsyncPoliciesResourceWithStreamingResponse,
+)
+from .configurations import (
+    ConfigurationsResource,
+    AsyncConfigurationsResource,
+    ConfigurationsResourceWithRawResponse,
+    AsyncConfigurationsResourceWithRawResponse,
+    ConfigurationsResourceWithStreamingResponse,
+    AsyncConfigurationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "ConfigurationsResource",
+    "AsyncConfigurationsResource",
+    "ConfigurationsResourceWithRawResponse",
+    "AsyncConfigurationsResourceWithRawResponse",
+    "ConfigurationsResourceWithStreamingResponse",
+    "AsyncConfigurationsResourceWithStreamingResponse",
+    "PoliciesResource",
+    "AsyncPoliciesResource",
+    "PoliciesResourceWithRawResponse",
+    "AsyncPoliciesResourceWithRawResponse",
+    "PoliciesResourceWithStreamingResponse",
+    "AsyncPoliciesResourceWithStreamingResponse",
+    "RunnersResource",
+    "AsyncRunnersResource",
+    "RunnersResourceWithRawResponse",
+    "AsyncRunnersResourceWithRawResponse",
+    "RunnersResourceWithStreamingResponse",
+    "AsyncRunnersResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/runners/configurations/__init__.py b/src/gitpod/resources/runners/configurations/__init__.py
new file mode 100644
index 0000000..848f99c
--- /dev/null
+++ b/src/gitpod/resources/runners/configurations/__init__.py
@@ -0,0 +1,75 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .schema import (
+    SchemaResource,
+    AsyncSchemaResource,
+    SchemaResourceWithRawResponse,
+    AsyncSchemaResourceWithRawResponse,
+    SchemaResourceWithStreamingResponse,
+    AsyncSchemaResourceWithStreamingResponse,
+)
+from .configurations import (
+    ConfigurationsResource,
+    AsyncConfigurationsResource,
+    ConfigurationsResourceWithRawResponse,
+    AsyncConfigurationsResourceWithRawResponse,
+    ConfigurationsResourceWithStreamingResponse,
+    AsyncConfigurationsResourceWithStreamingResponse,
+)
+from .scm_integrations import (
+    ScmIntegrationsResource,
+    AsyncScmIntegrationsResource,
+    ScmIntegrationsResourceWithRawResponse,
+    AsyncScmIntegrationsResourceWithRawResponse,
+    ScmIntegrationsResourceWithStreamingResponse,
+    AsyncScmIntegrationsResourceWithStreamingResponse,
+)
+from .environment_classes import (
+    EnvironmentClassesResource,
+    AsyncEnvironmentClassesResource,
+    EnvironmentClassesResourceWithRawResponse,
+    AsyncEnvironmentClassesResourceWithRawResponse,
+    EnvironmentClassesResourceWithStreamingResponse,
+    AsyncEnvironmentClassesResourceWithStreamingResponse,
+)
+from .host_authentication_tokens import (
+    HostAuthenticationTokensResource,
+    AsyncHostAuthenticationTokensResource,
+    HostAuthenticationTokensResourceWithRawResponse,
+    AsyncHostAuthenticationTokensResourceWithRawResponse,
+    HostAuthenticationTokensResourceWithStreamingResponse,
+    AsyncHostAuthenticationTokensResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "EnvironmentClassesResource",
+    "AsyncEnvironmentClassesResource",
+    "EnvironmentClassesResourceWithRawResponse",
+    "AsyncEnvironmentClassesResourceWithRawResponse",
+    "EnvironmentClassesResourceWithStreamingResponse",
+    "AsyncEnvironmentClassesResourceWithStreamingResponse",
+    "HostAuthenticationTokensResource",
+    "AsyncHostAuthenticationTokensResource",
+    "HostAuthenticationTokensResourceWithRawResponse",
+    "AsyncHostAuthenticationTokensResourceWithRawResponse",
+    "HostAuthenticationTokensResourceWithStreamingResponse",
+    "AsyncHostAuthenticationTokensResourceWithStreamingResponse",
+    "SchemaResource",
+    "AsyncSchemaResource",
+    "SchemaResourceWithRawResponse",
+    "AsyncSchemaResourceWithRawResponse",
+    "SchemaResourceWithStreamingResponse",
+    "AsyncSchemaResourceWithStreamingResponse",
+    "ScmIntegrationsResource",
+    "AsyncScmIntegrationsResource",
+    "ScmIntegrationsResourceWithRawResponse",
+    "AsyncScmIntegrationsResourceWithRawResponse",
+    "ScmIntegrationsResourceWithStreamingResponse",
+    "AsyncScmIntegrationsResourceWithStreamingResponse",
+    "ConfigurationsResource",
+    "AsyncConfigurationsResource",
+    "ConfigurationsResourceWithRawResponse",
+    "AsyncConfigurationsResourceWithRawResponse",
+    "ConfigurationsResourceWithStreamingResponse",
+    "AsyncConfigurationsResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/runners/configurations/configurations.py b/src/gitpod/resources/runners/configurations/configurations.py
new file mode 100644
index 0000000..b9b2c27
--- /dev/null
+++ b/src/gitpod/resources/runners/configurations/configurations.py
@@ -0,0 +1,356 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .schema import (
+    SchemaResource,
+    AsyncSchemaResource,
+    SchemaResourceWithRawResponse,
+    AsyncSchemaResourceWithRawResponse,
+    SchemaResourceWithStreamingResponse,
+    AsyncSchemaResourceWithStreamingResponse,
+)
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.runners import configuration_validate_params
+from .scm_integrations import (
+    ScmIntegrationsResource,
+    AsyncScmIntegrationsResource,
+    ScmIntegrationsResourceWithRawResponse,
+    AsyncScmIntegrationsResourceWithRawResponse,
+    ScmIntegrationsResourceWithStreamingResponse,
+    AsyncScmIntegrationsResourceWithStreamingResponse,
+)
+from .environment_classes import (
+    EnvironmentClassesResource,
+    AsyncEnvironmentClassesResource,
+    EnvironmentClassesResourceWithRawResponse,
+    AsyncEnvironmentClassesResourceWithRawResponse,
+    EnvironmentClassesResourceWithStreamingResponse,
+    AsyncEnvironmentClassesResourceWithStreamingResponse,
+)
+from .host_authentication_tokens import (
+    HostAuthenticationTokensResource,
+    AsyncHostAuthenticationTokensResource,
+    HostAuthenticationTokensResourceWithRawResponse,
+    AsyncHostAuthenticationTokensResourceWithRawResponse,
+    HostAuthenticationTokensResourceWithStreamingResponse,
+    AsyncHostAuthenticationTokensResourceWithStreamingResponse,
+)
+from ....types.shared_params.environment_class import EnvironmentClass
+from ....types.runners.configuration_validate_response import ConfigurationValidateResponse
+
+__all__ = ["ConfigurationsResource", "AsyncConfigurationsResource"]
+
+
+class ConfigurationsResource(SyncAPIResource):
+    @cached_property
+    def environment_classes(self) -> EnvironmentClassesResource:
+        return EnvironmentClassesResource(self._client)
+
+    @cached_property
+    def host_authentication_tokens(self) -> HostAuthenticationTokensResource:
+        return HostAuthenticationTokensResource(self._client)
+
+    @cached_property
+    def schema(self) -> SchemaResource:
+        return SchemaResource(self._client)
+
+    @cached_property
+    def scm_integrations(self) -> ScmIntegrationsResource:
+        return ScmIntegrationsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> ConfigurationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return ConfigurationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ConfigurationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return ConfigurationsResourceWithStreamingResponse(self)
+
+    def validate(
+        self,
+        *,
+        environment_class: EnvironmentClass | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        scm_integration: configuration_validate_params.ScmIntegration | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ConfigurationValidateResponse:
+        """
+        Validates a runner configuration.
+
+        Use this method to:
+
+        - Check configuration validity
+        - Verify integration settings
+        - Validate environment classes
+
+        ### Examples
+
+        - Validate SCM integration:
+
+          Checks if an SCM integration is valid.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          scmIntegration:
+            id: "integration-id"
+            scmId: "github"
+            host: "github.com"
+            oauthClientId: "client_id"
+            oauthPlaintextClientSecret: "client_secret"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/ValidateRunnerConfiguration",
+            body=maybe_transform(
+                {
+                    "environment_class": environment_class,
+                    "runner_id": runner_id,
+                    "scm_integration": scm_integration,
+                },
+                configuration_validate_params.ConfigurationValidateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ConfigurationValidateResponse,
+        )
+
+
+class AsyncConfigurationsResource(AsyncAPIResource):
+    @cached_property
+    def environment_classes(self) -> AsyncEnvironmentClassesResource:
+        return AsyncEnvironmentClassesResource(self._client)
+
+    @cached_property
+    def host_authentication_tokens(self) -> AsyncHostAuthenticationTokensResource:
+        return AsyncHostAuthenticationTokensResource(self._client)
+
+    @cached_property
+    def schema(self) -> AsyncSchemaResource:
+        return AsyncSchemaResource(self._client)
+
+    @cached_property
+    def scm_integrations(self) -> AsyncScmIntegrationsResource:
+        return AsyncScmIntegrationsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncConfigurationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncConfigurationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncConfigurationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncConfigurationsResourceWithStreamingResponse(self)
+
+    async def validate(
+        self,
+        *,
+        environment_class: EnvironmentClass | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        scm_integration: configuration_validate_params.ScmIntegration | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ConfigurationValidateResponse:
+        """
+        Validates a runner configuration.
+
+        Use this method to:
+
+        - Check configuration validity
+        - Verify integration settings
+        - Validate environment classes
+
+        ### Examples
+
+        - Validate SCM integration:
+
+          Checks if an SCM integration is valid.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          scmIntegration:
+            id: "integration-id"
+            scmId: "github"
+            host: "github.com"
+            oauthClientId: "client_id"
+            oauthPlaintextClientSecret: "client_secret"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/ValidateRunnerConfiguration",
+            body=await async_maybe_transform(
+                {
+                    "environment_class": environment_class,
+                    "runner_id": runner_id,
+                    "scm_integration": scm_integration,
+                },
+                configuration_validate_params.ConfigurationValidateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ConfigurationValidateResponse,
+        )
+
+
+class ConfigurationsResourceWithRawResponse:
+    def __init__(self, configurations: ConfigurationsResource) -> None:
+        self._configurations = configurations
+
+        self.validate = to_raw_response_wrapper(
+            configurations.validate,
+        )
+
+    @cached_property
+    def environment_classes(self) -> EnvironmentClassesResourceWithRawResponse:
+        return EnvironmentClassesResourceWithRawResponse(self._configurations.environment_classes)
+
+    @cached_property
+    def host_authentication_tokens(self) -> HostAuthenticationTokensResourceWithRawResponse:
+        return HostAuthenticationTokensResourceWithRawResponse(self._configurations.host_authentication_tokens)
+
+    @cached_property
+    def schema(self) -> SchemaResourceWithRawResponse:
+        return SchemaResourceWithRawResponse(self._configurations.schema)
+
+    @cached_property
+    def scm_integrations(self) -> ScmIntegrationsResourceWithRawResponse:
+        return ScmIntegrationsResourceWithRawResponse(self._configurations.scm_integrations)
+
+
+class AsyncConfigurationsResourceWithRawResponse:
+    def __init__(self, configurations: AsyncConfigurationsResource) -> None:
+        self._configurations = configurations
+
+        self.validate = async_to_raw_response_wrapper(
+            configurations.validate,
+        )
+
+    @cached_property
+    def environment_classes(self) -> AsyncEnvironmentClassesResourceWithRawResponse:
+        return AsyncEnvironmentClassesResourceWithRawResponse(self._configurations.environment_classes)
+
+    @cached_property
+    def host_authentication_tokens(self) -> AsyncHostAuthenticationTokensResourceWithRawResponse:
+        return AsyncHostAuthenticationTokensResourceWithRawResponse(self._configurations.host_authentication_tokens)
+
+    @cached_property
+    def schema(self) -> AsyncSchemaResourceWithRawResponse:
+        return AsyncSchemaResourceWithRawResponse(self._configurations.schema)
+
+    @cached_property
+    def scm_integrations(self) -> AsyncScmIntegrationsResourceWithRawResponse:
+        return AsyncScmIntegrationsResourceWithRawResponse(self._configurations.scm_integrations)
+
+
+class ConfigurationsResourceWithStreamingResponse:
+    def __init__(self, configurations: ConfigurationsResource) -> None:
+        self._configurations = configurations
+
+        self.validate = to_streamed_response_wrapper(
+            configurations.validate,
+        )
+
+    @cached_property
+    def environment_classes(self) -> EnvironmentClassesResourceWithStreamingResponse:
+        return EnvironmentClassesResourceWithStreamingResponse(self._configurations.environment_classes)
+
+    @cached_property
+    def host_authentication_tokens(self) -> HostAuthenticationTokensResourceWithStreamingResponse:
+        return HostAuthenticationTokensResourceWithStreamingResponse(self._configurations.host_authentication_tokens)
+
+    @cached_property
+    def schema(self) -> SchemaResourceWithStreamingResponse:
+        return SchemaResourceWithStreamingResponse(self._configurations.schema)
+
+    @cached_property
+    def scm_integrations(self) -> ScmIntegrationsResourceWithStreamingResponse:
+        return ScmIntegrationsResourceWithStreamingResponse(self._configurations.scm_integrations)
+
+
+class AsyncConfigurationsResourceWithStreamingResponse:
+    def __init__(self, configurations: AsyncConfigurationsResource) -> None:
+        self._configurations = configurations
+
+        self.validate = async_to_streamed_response_wrapper(
+            configurations.validate,
+        )
+
+    @cached_property
+    def environment_classes(self) -> AsyncEnvironmentClassesResourceWithStreamingResponse:
+        return AsyncEnvironmentClassesResourceWithStreamingResponse(self._configurations.environment_classes)
+
+    @cached_property
+    def host_authentication_tokens(self) -> AsyncHostAuthenticationTokensResourceWithStreamingResponse:
+        return AsyncHostAuthenticationTokensResourceWithStreamingResponse(
+            self._configurations.host_authentication_tokens
+        )
+
+    @cached_property
+    def schema(self) -> AsyncSchemaResourceWithStreamingResponse:
+        return AsyncSchemaResourceWithStreamingResponse(self._configurations.schema)
+
+    @cached_property
+    def scm_integrations(self) -> AsyncScmIntegrationsResourceWithStreamingResponse:
+        return AsyncScmIntegrationsResourceWithStreamingResponse(self._configurations.scm_integrations)
diff --git a/src/gitpod/resources/runners/configurations/environment_classes.py b/src/gitpod/resources/runners/configurations/environment_classes.py
new file mode 100644
index 0000000..ba2137c
--- /dev/null
+++ b/src/gitpod/resources/runners/configurations/environment_classes.py
@@ -0,0 +1,674 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ....pagination import SyncEnvironmentClassesPage, AsyncEnvironmentClassesPage
+from ...._base_client import AsyncPaginator, make_request_options
+from ....types.runners.configurations import (
+    environment_class_list_params,
+    environment_class_create_params,
+    environment_class_update_params,
+    environment_class_retrieve_params,
+)
+from ....types.shared.environment_class import EnvironmentClass
+from ....types.shared_params.field_value import FieldValue
+from ....types.runners.configurations.environment_class_create_response import EnvironmentClassCreateResponse
+from ....types.runners.configurations.environment_class_retrieve_response import EnvironmentClassRetrieveResponse
+
+__all__ = ["EnvironmentClassesResource", "AsyncEnvironmentClassesResource"]
+
+
+class EnvironmentClassesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> EnvironmentClassesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return EnvironmentClassesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> EnvironmentClassesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return EnvironmentClassesResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        configuration: Iterable[FieldValue] | NotGiven = NOT_GIVEN,
+        description: str | NotGiven = NOT_GIVEN,
+        display_name: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentClassCreateResponse:
+        """
+        Creates a new environment class for a runner.
+
+        Use this method to:
+
+        - Define compute resources
+        - Configure environment settings
+        - Set up runtime options
+
+        ### Examples
+
+        - Create environment class:
+
+          Creates a new environment configuration.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          displayName: "Large Instance"
+          description: "8 CPU, 16GB RAM"
+          configuration:
+            - key: "cpu"
+              value: "8"
+            - key: "memory"
+              value: "16384"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/CreateEnvironmentClass",
+            body=maybe_transform(
+                {
+                    "configuration": configuration,
+                    "description": description,
+                    "display_name": display_name,
+                    "runner_id": runner_id,
+                },
+                environment_class_create_params.EnvironmentClassCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentClassCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        environment_class_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentClassRetrieveResponse:
+        """
+        Gets details about a specific environment class.
+
+        Use this method to:
+
+        - View class configuration
+        - Check resource settings
+        - Verify availability
+
+        ### Examples
+
+        - Get class details:
+
+          Retrieves information about a specific class.
+
+          ```yaml
+          environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetEnvironmentClass",
+            body=maybe_transform(
+                {"environment_class_id": environment_class_id},
+                environment_class_retrieve_params.EnvironmentClassRetrieveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentClassRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        display_name: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: Optional[bool] | NotGiven = NOT_GIVEN,
+        environment_class_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an environment class.
+
+        Use this method to:
+
+        - Modify class settings
+        - Update resource limits
+        - Change availability
+
+        ### Examples
+
+        - Update class:
+
+          Changes class configuration.
+
+          ```yaml
+          environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          displayName: "Updated Large Instance"
+          description: "16 CPU, 32GB RAM"
+          enabled: true
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/UpdateEnvironmentClass",
+            body=maybe_transform(
+                {
+                    "description": description,
+                    "display_name": display_name,
+                    "enabled": enabled,
+                    "environment_class_id": environment_class_id,
+                },
+                environment_class_update_params.EnvironmentClassUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: environment_class_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: environment_class_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncEnvironmentClassesPage[EnvironmentClass]:
+        """
+        Lists environment classes with optional filtering.
+
+        Use this method to:
+
+        - View available classes
+        - Filter by capability
+        - Check enabled status
+
+        ### Examples
+
+        - List all classes:
+
+          Shows all environment classes.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter enabled classes:
+
+          Lists only enabled environment classes.
+
+          ```yaml
+          filter:
+            enabled: true
+          pagination:
+            pageSize: 20
+          ```
+
+          buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE
+
+        Args:
+          pagination: pagination contains the pagination options for listing environment classes
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerConfigurationService/ListEnvironmentClasses",
+            page=SyncEnvironmentClassesPage[EnvironmentClass],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                environment_class_list_params.EnvironmentClassListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    environment_class_list_params.EnvironmentClassListParams,
+                ),
+            ),
+            model=EnvironmentClass,
+            method="post",
+        )
+
+
+class AsyncEnvironmentClassesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncEnvironmentClassesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncEnvironmentClassesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncEnvironmentClassesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncEnvironmentClassesResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        configuration: Iterable[FieldValue] | NotGiven = NOT_GIVEN,
+        description: str | NotGiven = NOT_GIVEN,
+        display_name: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentClassCreateResponse:
+        """
+        Creates a new environment class for a runner.
+
+        Use this method to:
+
+        - Define compute resources
+        - Configure environment settings
+        - Set up runtime options
+
+        ### Examples
+
+        - Create environment class:
+
+          Creates a new environment configuration.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          displayName: "Large Instance"
+          description: "8 CPU, 16GB RAM"
+          configuration:
+            - key: "cpu"
+              value: "8"
+            - key: "memory"
+              value: "16384"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/CreateEnvironmentClass",
+            body=await async_maybe_transform(
+                {
+                    "configuration": configuration,
+                    "description": description,
+                    "display_name": display_name,
+                    "runner_id": runner_id,
+                },
+                environment_class_create_params.EnvironmentClassCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentClassCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        environment_class_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EnvironmentClassRetrieveResponse:
+        """
+        Gets details about a specific environment class.
+
+        Use this method to:
+
+        - View class configuration
+        - Check resource settings
+        - Verify availability
+
+        ### Examples
+
+        - Get class details:
+
+          Retrieves information about a specific class.
+
+          ```yaml
+          environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetEnvironmentClass",
+            body=await async_maybe_transform(
+                {"environment_class_id": environment_class_id},
+                environment_class_retrieve_params.EnvironmentClassRetrieveParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EnvironmentClassRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        display_name: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: Optional[bool] | NotGiven = NOT_GIVEN,
+        environment_class_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an environment class.
+
+        Use this method to:
+
+        - Modify class settings
+        - Update resource limits
+        - Change availability
+
+        ### Examples
+
+        - Update class:
+
+          Changes class configuration.
+
+          ```yaml
+          environmentClassId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          displayName: "Updated Large Instance"
+          description: "16 CPU, 32GB RAM"
+          enabled: true
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/UpdateEnvironmentClass",
+            body=await async_maybe_transform(
+                {
+                    "description": description,
+                    "display_name": display_name,
+                    "enabled": enabled,
+                    "environment_class_id": environment_class_id,
+                },
+                environment_class_update_params.EnvironmentClassUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: environment_class_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: environment_class_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[EnvironmentClass, AsyncEnvironmentClassesPage[EnvironmentClass]]:
+        """
+        Lists environment classes with optional filtering.
+
+        Use this method to:
+
+        - View available classes
+        - Filter by capability
+        - Check enabled status
+
+        ### Examples
+
+        - List all classes:
+
+          Shows all environment classes.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter enabled classes:
+
+          Lists only enabled environment classes.
+
+          ```yaml
+          filter:
+            enabled: true
+          pagination:
+            pageSize: 20
+          ```
+
+          buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE
+
+        Args:
+          pagination: pagination contains the pagination options for listing environment classes
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerConfigurationService/ListEnvironmentClasses",
+            page=AsyncEnvironmentClassesPage[EnvironmentClass],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                environment_class_list_params.EnvironmentClassListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    environment_class_list_params.EnvironmentClassListParams,
+                ),
+            ),
+            model=EnvironmentClass,
+            method="post",
+        )
+
+
+class EnvironmentClassesResourceWithRawResponse:
+    def __init__(self, environment_classes: EnvironmentClassesResource) -> None:
+        self._environment_classes = environment_classes
+
+        self.create = to_raw_response_wrapper(
+            environment_classes.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            environment_classes.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            environment_classes.update,
+        )
+        self.list = to_raw_response_wrapper(
+            environment_classes.list,
+        )
+
+
+class AsyncEnvironmentClassesResourceWithRawResponse:
+    def __init__(self, environment_classes: AsyncEnvironmentClassesResource) -> None:
+        self._environment_classes = environment_classes
+
+        self.create = async_to_raw_response_wrapper(
+            environment_classes.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            environment_classes.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            environment_classes.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            environment_classes.list,
+        )
+
+
+class EnvironmentClassesResourceWithStreamingResponse:
+    def __init__(self, environment_classes: EnvironmentClassesResource) -> None:
+        self._environment_classes = environment_classes
+
+        self.create = to_streamed_response_wrapper(
+            environment_classes.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            environment_classes.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            environment_classes.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            environment_classes.list,
+        )
+
+
+class AsyncEnvironmentClassesResourceWithStreamingResponse:
+    def __init__(self, environment_classes: AsyncEnvironmentClassesResource) -> None:
+        self._environment_classes = environment_classes
+
+        self.create = async_to_streamed_response_wrapper(
+            environment_classes.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            environment_classes.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            environment_classes.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            environment_classes.list,
+        )
diff --git a/src/gitpod/resources/runners/configurations/host_authentication_tokens.py b/src/gitpod/resources/runners/configurations/host_authentication_tokens.py
new file mode 100644
index 0000000..af044ec
--- /dev/null
+++ b/src/gitpod/resources/runners/configurations/host_authentication_tokens.py
@@ -0,0 +1,1149 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ....pagination import SyncTokensPage, AsyncTokensPage
+from ...._base_client import AsyncPaginator, make_request_options
+from ....types.runners.configurations import (
+    HostAuthenticationTokenSource,
+    host_authentication_token_list_params,
+    host_authentication_token_create_params,
+    host_authentication_token_delete_params,
+    host_authentication_token_update_params,
+    host_authentication_token_retrieve_params,
+)
+from ....types.runners.configurations.host_authentication_token import HostAuthenticationToken
+from ....types.runners.configurations.host_authentication_token_source import HostAuthenticationTokenSource
+from ....types.runners.configurations.host_authentication_token_create_response import (
+    HostAuthenticationTokenCreateResponse,
+)
+from ....types.runners.configurations.host_authentication_token_retrieve_response import (
+    HostAuthenticationTokenRetrieveResponse,
+)
+
+__all__ = ["HostAuthenticationTokensResource", "AsyncHostAuthenticationTokensResource"]
+
+
+class HostAuthenticationTokensResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> HostAuthenticationTokensResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return HostAuthenticationTokensResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> HostAuthenticationTokensResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return HostAuthenticationTokensResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime] | NotGiven = NOT_GIVEN,
+        host: str | NotGiven = NOT_GIVEN,
+        refresh_token: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        source: HostAuthenticationTokenSource | NotGiven = NOT_GIVEN,
+        user_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HostAuthenticationTokenCreateResponse:
+        """
+        Creates a new authentication token for accessing remote hosts.
+
+        Use this method to:
+
+        - Set up SCM authentication
+        - Configure OAuth credentials
+        - Manage PAT tokens
+
+        ### Examples
+
+        - Create OAuth token:
+
+          Creates a new OAuth-based authentication token.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          host: "github.com"
+          token: "gho_xxxxxxxxxxxx"
+          source: HOST_AUTHENTICATION_TOKEN_SOURCE_OAUTH
+          expiresAt: "2024-12-31T23:59:59Z"
+          refreshToken: "ghr_xxxxxxxxxxxx"
+          ```
+
+        Args:
+          expires_at: A Timestamp represents a point in time independent of any time zone or local
+              calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+              resolution. The count is relative to an epoch at UTC midnight on January 1,
+              1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+              backwards to year one.
+
+              All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+              second table is needed for interpretation, using a
+              [24-hour linear smear](https://developers.google.com/time/smear).
+
+              The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+              restricting to that range, we ensure that we can convert to and from
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+              # Examples
+
+              Example 1: Compute Timestamp from POSIX `time()`.
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(time(NULL));
+                   timestamp.set_nanos(0);
+
+              Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+                   struct timeval tv;
+                   gettimeofday(&tv, NULL);
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(tv.tv_sec);
+                   timestamp.set_nanos(tv.tv_usec * 1000);
+
+              Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+                   FILETIME ft;
+                   GetSystemTimeAsFileTime(&ft);
+                   UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+                   // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+                   // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+                   Timestamp timestamp;
+                   timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+                   timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+              Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+                   long millis = System.currentTimeMillis();
+
+                   Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+                       .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+              Example 5: Compute Timestamp from Java `Instant.now()`.
+
+                   Instant now = Instant.now();
+
+                   Timestamp timestamp =
+                       Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                           .setNanos(now.getNano()).build();
+
+              Example 6: Compute Timestamp from current time in Python.
+
+                   timestamp = Timestamp()
+                   timestamp.GetCurrentTime()
+
+              # JSON Mapping
+
+              In JSON format, the Timestamp type is encoded as a string in the
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+              "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+              expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+              zero-padded to two digits each. The fractional seconds, which can go up to 9
+              digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+              indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+              serializer should always use UTC (as indicated by "Z") when printing the
+              Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+              other timezones (as indicated by an offset).
+
+              For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+              January 15, 2017.
+
+              In JavaScript, one can convert a Date object to this format using the standard
+              [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+              method. In Python, a standard `datetime.datetime` object can be converted to
+              this format using
+              [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+              time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+              Joda Time's
+              [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+              to obtain a formatter capable of generating timestamps in this format.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/CreateHostAuthenticationToken",
+            body=maybe_transform(
+                {
+                    "token": token,
+                    "expires_at": expires_at,
+                    "host": host,
+                    "refresh_token": refresh_token,
+                    "runner_id": runner_id,
+                    "source": source,
+                    "user_id": user_id,
+                },
+                host_authentication_token_create_params.HostAuthenticationTokenCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HostAuthenticationTokenCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HostAuthenticationTokenRetrieveResponse:
+        """
+        Gets details about a specific host authentication token.
+
+        Use this method to:
+
+        - View token information
+        - Check token expiration
+        - Verify token validity
+
+        ### Examples
+
+        - Get token details:
+
+          Retrieves information about a specific token.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetHostAuthenticationToken",
+            body=maybe_transform(
+                {"id": id}, host_authentication_token_retrieve_params.HostAuthenticationTokenRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HostAuthenticationTokenRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        token: Optional[str] | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        refresh_token: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an existing host authentication token.
+
+        Use this method to:
+
+        - Refresh token values
+        - Update expiration
+        - Modify token settings
+
+        ### Examples
+
+        - Update token:
+
+          Updates token value and expiration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          token: "gho_xxxxxxxxxxxx"
+          expiresAt: "2024-12-31T23:59:59Z"
+          refreshToken: "ghr_xxxxxxxxxxxx"
+          ```
+
+        Args:
+          expires_at: A Timestamp represents a point in time independent of any time zone or local
+              calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+              resolution. The count is relative to an epoch at UTC midnight on January 1,
+              1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+              backwards to year one.
+
+              All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+              second table is needed for interpretation, using a
+              [24-hour linear smear](https://developers.google.com/time/smear).
+
+              The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+              restricting to that range, we ensure that we can convert to and from
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+              # Examples
+
+              Example 1: Compute Timestamp from POSIX `time()`.
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(time(NULL));
+                   timestamp.set_nanos(0);
+
+              Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+                   struct timeval tv;
+                   gettimeofday(&tv, NULL);
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(tv.tv_sec);
+                   timestamp.set_nanos(tv.tv_usec * 1000);
+
+              Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+                   FILETIME ft;
+                   GetSystemTimeAsFileTime(&ft);
+                   UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+                   // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+                   // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+                   Timestamp timestamp;
+                   timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+                   timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+              Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+                   long millis = System.currentTimeMillis();
+
+                   Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+                       .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+              Example 5: Compute Timestamp from Java `Instant.now()`.
+
+                   Instant now = Instant.now();
+
+                   Timestamp timestamp =
+                       Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                           .setNanos(now.getNano()).build();
+
+              Example 6: Compute Timestamp from current time in Python.
+
+                   timestamp = Timestamp()
+                   timestamp.GetCurrentTime()
+
+              # JSON Mapping
+
+              In JSON format, the Timestamp type is encoded as a string in the
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+              "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+              expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+              zero-padded to two digits each. The fractional seconds, which can go up to 9
+              digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+              indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+              serializer should always use UTC (as indicated by "Z") when printing the
+              Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+              other timezones (as indicated by an offset).
+
+              For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+              January 15, 2017.
+
+              In JavaScript, one can convert a Date object to this format using the standard
+              [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+              method. In Python, a standard `datetime.datetime` object can be converted to
+              this format using
+              [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+              time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+              Joda Time's
+              [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+              to obtain a formatter capable of generating timestamps in this format.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/UpdateHostAuthenticationToken",
+            body=maybe_transform(
+                {
+                    "id": id,
+                    "token": token,
+                    "expires_at": expires_at,
+                    "refresh_token": refresh_token,
+                },
+                host_authentication_token_update_params.HostAuthenticationTokenUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: host_authentication_token_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: host_authentication_token_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncTokensPage[HostAuthenticationToken]:
+        """
+        Lists host authentication tokens with optional filtering.
+
+        Use this method to:
+
+        - View all tokens
+        - Filter by runner or user
+        - Monitor token status
+
+        ### Examples
+
+        - List all tokens:
+
+          Shows all tokens with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by runner:
+
+          Lists tokens for a specific runner.
+
+          ```yaml
+          filter:
+            runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerConfigurationService/ListHostAuthenticationTokens",
+            page=SyncTokensPage[HostAuthenticationToken],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                host_authentication_token_list_params.HostAuthenticationTokenListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    host_authentication_token_list_params.HostAuthenticationTokenListParams,
+                ),
+            ),
+            model=HostAuthenticationToken,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a host authentication token.
+
+        Use this method to:
+
+        - Remove unused tokens
+        - Revoke access
+        - Clean up expired tokens
+
+        ### Examples
+
+        - Delete token:
+
+          Permanently removes a token.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/DeleteHostAuthenticationToken",
+            body=maybe_transform(
+                {"id": id}, host_authentication_token_delete_params.HostAuthenticationTokenDeleteParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncHostAuthenticationTokensResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncHostAuthenticationTokensResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncHostAuthenticationTokensResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncHostAuthenticationTokensResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncHostAuthenticationTokensResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime] | NotGiven = NOT_GIVEN,
+        host: str | NotGiven = NOT_GIVEN,
+        refresh_token: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        source: HostAuthenticationTokenSource | NotGiven = NOT_GIVEN,
+        user_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HostAuthenticationTokenCreateResponse:
+        """
+        Creates a new authentication token for accessing remote hosts.
+
+        Use this method to:
+
+        - Set up SCM authentication
+        - Configure OAuth credentials
+        - Manage PAT tokens
+
+        ### Examples
+
+        - Create OAuth token:
+
+          Creates a new OAuth-based authentication token.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          host: "github.com"
+          token: "gho_xxxxxxxxxxxx"
+          source: HOST_AUTHENTICATION_TOKEN_SOURCE_OAUTH
+          expiresAt: "2024-12-31T23:59:59Z"
+          refreshToken: "ghr_xxxxxxxxxxxx"
+          ```
+
+        Args:
+          expires_at: A Timestamp represents a point in time independent of any time zone or local
+              calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+              resolution. The count is relative to an epoch at UTC midnight on January 1,
+              1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+              backwards to year one.
+
+              All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+              second table is needed for interpretation, using a
+              [24-hour linear smear](https://developers.google.com/time/smear).
+
+              The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+              restricting to that range, we ensure that we can convert to and from
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+              # Examples
+
+              Example 1: Compute Timestamp from POSIX `time()`.
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(time(NULL));
+                   timestamp.set_nanos(0);
+
+              Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+                   struct timeval tv;
+                   gettimeofday(&tv, NULL);
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(tv.tv_sec);
+                   timestamp.set_nanos(tv.tv_usec * 1000);
+
+              Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+                   FILETIME ft;
+                   GetSystemTimeAsFileTime(&ft);
+                   UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+                   // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+                   // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+                   Timestamp timestamp;
+                   timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+                   timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+              Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+                   long millis = System.currentTimeMillis();
+
+                   Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+                       .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+              Example 5: Compute Timestamp from Java `Instant.now()`.
+
+                   Instant now = Instant.now();
+
+                   Timestamp timestamp =
+                       Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                           .setNanos(now.getNano()).build();
+
+              Example 6: Compute Timestamp from current time in Python.
+
+                   timestamp = Timestamp()
+                   timestamp.GetCurrentTime()
+
+              # JSON Mapping
+
+              In JSON format, the Timestamp type is encoded as a string in the
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+              "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+              expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+              zero-padded to two digits each. The fractional seconds, which can go up to 9
+              digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+              indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+              serializer should always use UTC (as indicated by "Z") when printing the
+              Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+              other timezones (as indicated by an offset).
+
+              For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+              January 15, 2017.
+
+              In JavaScript, one can convert a Date object to this format using the standard
+              [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+              method. In Python, a standard `datetime.datetime` object can be converted to
+              this format using
+              [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+              time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+              Joda Time's
+              [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+              to obtain a formatter capable of generating timestamps in this format.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/CreateHostAuthenticationToken",
+            body=await async_maybe_transform(
+                {
+                    "token": token,
+                    "expires_at": expires_at,
+                    "host": host,
+                    "refresh_token": refresh_token,
+                    "runner_id": runner_id,
+                    "source": source,
+                    "user_id": user_id,
+                },
+                host_authentication_token_create_params.HostAuthenticationTokenCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HostAuthenticationTokenCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HostAuthenticationTokenRetrieveResponse:
+        """
+        Gets details about a specific host authentication token.
+
+        Use this method to:
+
+        - View token information
+        - Check token expiration
+        - Verify token validity
+
+        ### Examples
+
+        - Get token details:
+
+          Retrieves information about a specific token.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetHostAuthenticationToken",
+            body=await async_maybe_transform(
+                {"id": id}, host_authentication_token_retrieve_params.HostAuthenticationTokenRetrieveParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HostAuthenticationTokenRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        token: Optional[str] | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        refresh_token: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an existing host authentication token.
+
+        Use this method to:
+
+        - Refresh token values
+        - Update expiration
+        - Modify token settings
+
+        ### Examples
+
+        - Update token:
+
+          Updates token value and expiration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          token: "gho_xxxxxxxxxxxx"
+          expiresAt: "2024-12-31T23:59:59Z"
+          refreshToken: "ghr_xxxxxxxxxxxx"
+          ```
+
+        Args:
+          expires_at: A Timestamp represents a point in time independent of any time zone or local
+              calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+              resolution. The count is relative to an epoch at UTC midnight on January 1,
+              1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+              backwards to year one.
+
+              All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+              second table is needed for interpretation, using a
+              [24-hour linear smear](https://developers.google.com/time/smear).
+
+              The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+              restricting to that range, we ensure that we can convert to and from
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+              # Examples
+
+              Example 1: Compute Timestamp from POSIX `time()`.
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(time(NULL));
+                   timestamp.set_nanos(0);
+
+              Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+                   struct timeval tv;
+                   gettimeofday(&tv, NULL);
+
+                   Timestamp timestamp;
+                   timestamp.set_seconds(tv.tv_sec);
+                   timestamp.set_nanos(tv.tv_usec * 1000);
+
+              Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+                   FILETIME ft;
+                   GetSystemTimeAsFileTime(&ft);
+                   UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+                   // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+                   // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+                   Timestamp timestamp;
+                   timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+                   timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+              Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+                   long millis = System.currentTimeMillis();
+
+                   Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+                       .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+              Example 5: Compute Timestamp from Java `Instant.now()`.
+
+                   Instant now = Instant.now();
+
+                   Timestamp timestamp =
+                       Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                           .setNanos(now.getNano()).build();
+
+              Example 6: Compute Timestamp from current time in Python.
+
+                   timestamp = Timestamp()
+                   timestamp.GetCurrentTime()
+
+              # JSON Mapping
+
+              In JSON format, the Timestamp type is encoded as a string in the
+              [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+              "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+              expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+              zero-padded to two digits each. The fractional seconds, which can go up to 9
+              digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+              indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+              serializer should always use UTC (as indicated by "Z") when printing the
+              Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+              other timezones (as indicated by an offset).
+
+              For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+              January 15, 2017.
+
+              In JavaScript, one can convert a Date object to this format using the standard
+              [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+              method. In Python, a standard `datetime.datetime` object can be converted to
+              this format using
+              [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+              time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+              Joda Time's
+              [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+              to obtain a formatter capable of generating timestamps in this format.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/UpdateHostAuthenticationToken",
+            body=await async_maybe_transform(
+                {
+                    "id": id,
+                    "token": token,
+                    "expires_at": expires_at,
+                    "refresh_token": refresh_token,
+                },
+                host_authentication_token_update_params.HostAuthenticationTokenUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: host_authentication_token_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: host_authentication_token_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[HostAuthenticationToken, AsyncTokensPage[HostAuthenticationToken]]:
+        """
+        Lists host authentication tokens with optional filtering.
+
+        Use this method to:
+
+        - View all tokens
+        - Filter by runner or user
+        - Monitor token status
+
+        ### Examples
+
+        - List all tokens:
+
+          Shows all tokens with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by runner:
+
+          Lists tokens for a specific runner.
+
+          ```yaml
+          filter:
+            runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerConfigurationService/ListHostAuthenticationTokens",
+            page=AsyncTokensPage[HostAuthenticationToken],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                host_authentication_token_list_params.HostAuthenticationTokenListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    host_authentication_token_list_params.HostAuthenticationTokenListParams,
+                ),
+            ),
+            model=HostAuthenticationToken,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a host authentication token.
+
+        Use this method to:
+
+        - Remove unused tokens
+        - Revoke access
+        - Clean up expired tokens
+
+        ### Examples
+
+        - Delete token:
+
+          Permanently removes a token.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/DeleteHostAuthenticationToken",
+            body=await async_maybe_transform(
+                {"id": id}, host_authentication_token_delete_params.HostAuthenticationTokenDeleteParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class HostAuthenticationTokensResourceWithRawResponse:
+    def __init__(self, host_authentication_tokens: HostAuthenticationTokensResource) -> None:
+        self._host_authentication_tokens = host_authentication_tokens
+
+        self.create = to_raw_response_wrapper(
+            host_authentication_tokens.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            host_authentication_tokens.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            host_authentication_tokens.update,
+        )
+        self.list = to_raw_response_wrapper(
+            host_authentication_tokens.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            host_authentication_tokens.delete,
+        )
+
+
+class AsyncHostAuthenticationTokensResourceWithRawResponse:
+    def __init__(self, host_authentication_tokens: AsyncHostAuthenticationTokensResource) -> None:
+        self._host_authentication_tokens = host_authentication_tokens
+
+        self.create = async_to_raw_response_wrapper(
+            host_authentication_tokens.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            host_authentication_tokens.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            host_authentication_tokens.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            host_authentication_tokens.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            host_authentication_tokens.delete,
+        )
+
+
+class HostAuthenticationTokensResourceWithStreamingResponse:
+    def __init__(self, host_authentication_tokens: HostAuthenticationTokensResource) -> None:
+        self._host_authentication_tokens = host_authentication_tokens
+
+        self.create = to_streamed_response_wrapper(
+            host_authentication_tokens.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            host_authentication_tokens.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            host_authentication_tokens.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            host_authentication_tokens.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            host_authentication_tokens.delete,
+        )
+
+
+class AsyncHostAuthenticationTokensResourceWithStreamingResponse:
+    def __init__(self, host_authentication_tokens: AsyncHostAuthenticationTokensResource) -> None:
+        self._host_authentication_tokens = host_authentication_tokens
+
+        self.create = async_to_streamed_response_wrapper(
+            host_authentication_tokens.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            host_authentication_tokens.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            host_authentication_tokens.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            host_authentication_tokens.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            host_authentication_tokens.delete,
+        )
diff --git a/src/gitpod/resources/runners/configurations/schema.py b/src/gitpod/resources/runners/configurations/schema.py
new file mode 100644
index 0000000..06963e1
--- /dev/null
+++ b/src/gitpod/resources/runners/configurations/schema.py
@@ -0,0 +1,195 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.runners.configurations import schema_retrieve_params
+from ....types.runners.configurations.schema_retrieve_response import SchemaRetrieveResponse
+
+__all__ = ["SchemaResource", "AsyncSchemaResource"]
+
+
+class SchemaResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> SchemaResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return SchemaResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> SchemaResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return SchemaResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        *,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SchemaRetrieveResponse:
+        """
+        Gets the latest runner configuration schema.
+
+        Use this method to:
+
+        - View available settings
+        - Check configuration options
+        - Validate configurations
+
+        ### Examples
+
+        - Get schema:
+
+          Retrieves configuration schema for a runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetRunnerConfigurationSchema",
+            body=maybe_transform({"runner_id": runner_id}, schema_retrieve_params.SchemaRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SchemaRetrieveResponse,
+        )
+
+
+class AsyncSchemaResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncSchemaResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncSchemaResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncSchemaResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncSchemaResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        *,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SchemaRetrieveResponse:
+        """
+        Gets the latest runner configuration schema.
+
+        Use this method to:
+
+        - View available settings
+        - Check configuration options
+        - Validate configurations
+
+        ### Examples
+
+        - Get schema:
+
+          Retrieves configuration schema for a runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetRunnerConfigurationSchema",
+            body=await async_maybe_transform({"runner_id": runner_id}, schema_retrieve_params.SchemaRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SchemaRetrieveResponse,
+        )
+
+
+class SchemaResourceWithRawResponse:
+    def __init__(self, schema: SchemaResource) -> None:
+        self._schema = schema
+
+        self.retrieve = to_raw_response_wrapper(
+            schema.retrieve,
+        )
+
+
+class AsyncSchemaResourceWithRawResponse:
+    def __init__(self, schema: AsyncSchemaResource) -> None:
+        self._schema = schema
+
+        self.retrieve = async_to_raw_response_wrapper(
+            schema.retrieve,
+        )
+
+
+class SchemaResourceWithStreamingResponse:
+    def __init__(self, schema: SchemaResource) -> None:
+        self._schema = schema
+
+        self.retrieve = to_streamed_response_wrapper(
+            schema.retrieve,
+        )
+
+
+class AsyncSchemaResourceWithStreamingResponse:
+    def __init__(self, schema: AsyncSchemaResource) -> None:
+        self._schema = schema
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            schema.retrieve,
+        )
diff --git a/src/gitpod/resources/runners/configurations/scm_integrations.py b/src/gitpod/resources/runners/configurations/scm_integrations.py
new file mode 100644
index 0000000..2947b2e
--- /dev/null
+++ b/src/gitpod/resources/runners/configurations/scm_integrations.py
@@ -0,0 +1,826 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ....pagination import SyncIntegrationsPage, AsyncIntegrationsPage
+from ...._base_client import AsyncPaginator, make_request_options
+from ....types.runners.configurations import (
+    scm_integration_list_params,
+    scm_integration_create_params,
+    scm_integration_delete_params,
+    scm_integration_update_params,
+    scm_integration_retrieve_params,
+)
+from ....types.runners.configurations.scm_integration import ScmIntegration
+from ....types.runners.configurations.scm_integration_create_response import ScmIntegrationCreateResponse
+from ....types.runners.configurations.scm_integration_retrieve_response import ScmIntegrationRetrieveResponse
+
+__all__ = ["ScmIntegrationsResource", "AsyncScmIntegrationsResource"]
+
+
+class ScmIntegrationsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> ScmIntegrationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return ScmIntegrationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ScmIntegrationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return ScmIntegrationsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        host: str | NotGiven = NOT_GIVEN,
+        issuer_url: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_client_id: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_plaintext_client_secret: Optional[str] | NotGiven = NOT_GIVEN,
+        pat: bool | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        scm_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ScmIntegrationCreateResponse:
+        """
+        Creates a new SCM integration for a runner.
+
+        Use this method to:
+
+        - Configure source control access
+        - Set up repository integrations
+        - Enable code synchronization
+
+        ### Examples
+
+        - Create GitHub integration:
+
+          Sets up GitHub SCM integration.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          scmId: "github"
+          host: "github.com"
+          oauthClientId: "client_id"
+          oauthPlaintextClientSecret: "client_secret"
+          ```
+
+        Args:
+          issuer_url: issuer_url can be set to override the authentication provider URL, if it doesn't
+              match the SCM host.
+
+          oauth_client_id: oauth_client_id is the OAuth app's client ID, if OAuth is configured. If
+              configured, oauth_plaintext_client_secret must also be set.
+
+          oauth_plaintext_client_secret: oauth_plaintext_client_secret is the OAuth app's client secret in clear text.
+              This will first be encrypted with the runner's public key before being stored.
+
+          scm_id: scm_id references the scm_id in the runner's configuration schema that this
+              integration is for
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/CreateSCMIntegration",
+            body=maybe_transform(
+                {
+                    "host": host,
+                    "issuer_url": issuer_url,
+                    "oauth_client_id": oauth_client_id,
+                    "oauth_plaintext_client_secret": oauth_plaintext_client_secret,
+                    "pat": pat,
+                    "runner_id": runner_id,
+                    "scm_id": scm_id,
+                },
+                scm_integration_create_params.ScmIntegrationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ScmIntegrationCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ScmIntegrationRetrieveResponse:
+        """
+        Gets details about a specific SCM integration.
+
+        Use this method to:
+
+        - View integration settings
+        - Check integration status
+        - Verify configuration
+
+        ### Examples
+
+        - Get integration details:
+
+          Retrieves information about a specific integration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetSCMIntegration",
+            body=maybe_transform({"id": id}, scm_integration_retrieve_params.ScmIntegrationRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ScmIntegrationRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        issuer_url: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_client_id: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_plaintext_client_secret: Optional[str] | NotGiven = NOT_GIVEN,
+        pat: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an existing SCM integration.
+
+        Use this method to:
+
+        - Modify integration settings
+        - Update credentials
+        - Change configuration
+
+        ### Examples
+
+        - Update integration:
+
+          Updates OAuth credentials.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          oauthClientId: "new_client_id"
+          oauthPlaintextClientSecret: "new_client_secret"
+          ```
+
+        Args:
+          issuer_url: issuer_url can be set to override the authentication provider URL, if it doesn't
+              match the SCM host.
+
+          oauth_client_id: oauth_client_id can be set to update the OAuth app's client ID. If an empty
+              string is set, the OAuth configuration will be removed (regardless of whether a
+              client secret is set), and any existing Host Authentication Tokens for the SCM
+              integration's runner and host that were created using the OAuth app will be
+              deleted. This might lead to users being unable to access their repositories
+              until they re-authenticate.
+
+          oauth_plaintext_client_secret: oauth_plaintext_client_secret can be set to update the OAuth app's client
+              secret. The cleartext secret will be encrypted with the runner's public key
+              before being stored.
+
+          pat: pat can be set to enable or disable Personal Access Tokens support. When
+              disabling PATs, any existing Host Authentication Tokens for the SCM
+              integration's runner and host that were created using a PAT will be deleted.
+              This might lead to users being unable to access their repositories until they
+              re-authenticate.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/UpdateSCMIntegration",
+            body=maybe_transform(
+                {
+                    "id": id,
+                    "issuer_url": issuer_url,
+                    "oauth_client_id": oauth_client_id,
+                    "oauth_plaintext_client_secret": oauth_plaintext_client_secret,
+                    "pat": pat,
+                },
+                scm_integration_update_params.ScmIntegrationUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: scm_integration_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: scm_integration_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncIntegrationsPage[ScmIntegration]:
+        """
+        Lists SCM integrations for a runner.
+
+        Use this method to:
+
+        - View all integrations
+        - Monitor integration status
+        - Check available SCMs
+
+        ### Examples
+
+        - List integrations:
+
+          Shows all SCM integrations.
+
+          ```yaml
+          filter:
+            runnerIds: ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing scm integrations
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerConfigurationService/ListSCMIntegrations",
+            page=SyncIntegrationsPage[ScmIntegration],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                scm_integration_list_params.ScmIntegrationListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    scm_integration_list_params.ScmIntegrationListParams,
+                ),
+            ),
+            model=ScmIntegration,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes an SCM integration.
+
+        Use this method to:
+
+        - Remove unused integrations
+        - Clean up configurations
+        - Revoke SCM access
+
+        ### Examples
+
+        - Delete integration:
+
+          Removes an SCM integration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerConfigurationService/DeleteSCMIntegration",
+            body=maybe_transform({"id": id}, scm_integration_delete_params.ScmIntegrationDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncScmIntegrationsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncScmIntegrationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncScmIntegrationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncScmIntegrationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncScmIntegrationsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        host: str | NotGiven = NOT_GIVEN,
+        issuer_url: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_client_id: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_plaintext_client_secret: Optional[str] | NotGiven = NOT_GIVEN,
+        pat: bool | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        scm_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ScmIntegrationCreateResponse:
+        """
+        Creates a new SCM integration for a runner.
+
+        Use this method to:
+
+        - Configure source control access
+        - Set up repository integrations
+        - Enable code synchronization
+
+        ### Examples
+
+        - Create GitHub integration:
+
+          Sets up GitHub SCM integration.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          scmId: "github"
+          host: "github.com"
+          oauthClientId: "client_id"
+          oauthPlaintextClientSecret: "client_secret"
+          ```
+
+        Args:
+          issuer_url: issuer_url can be set to override the authentication provider URL, if it doesn't
+              match the SCM host.
+
+          oauth_client_id: oauth_client_id is the OAuth app's client ID, if OAuth is configured. If
+              configured, oauth_plaintext_client_secret must also be set.
+
+          oauth_plaintext_client_secret: oauth_plaintext_client_secret is the OAuth app's client secret in clear text.
+              This will first be encrypted with the runner's public key before being stored.
+
+          scm_id: scm_id references the scm_id in the runner's configuration schema that this
+              integration is for
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/CreateSCMIntegration",
+            body=await async_maybe_transform(
+                {
+                    "host": host,
+                    "issuer_url": issuer_url,
+                    "oauth_client_id": oauth_client_id,
+                    "oauth_plaintext_client_secret": oauth_plaintext_client_secret,
+                    "pat": pat,
+                    "runner_id": runner_id,
+                    "scm_id": scm_id,
+                },
+                scm_integration_create_params.ScmIntegrationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ScmIntegrationCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ScmIntegrationRetrieveResponse:
+        """
+        Gets details about a specific SCM integration.
+
+        Use this method to:
+
+        - View integration settings
+        - Check integration status
+        - Verify configuration
+
+        ### Examples
+
+        - Get integration details:
+
+          Retrieves information about a specific integration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/GetSCMIntegration",
+            body=await async_maybe_transform({"id": id}, scm_integration_retrieve_params.ScmIntegrationRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ScmIntegrationRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        issuer_url: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_client_id: Optional[str] | NotGiven = NOT_GIVEN,
+        oauth_plaintext_client_secret: Optional[str] | NotGiven = NOT_GIVEN,
+        pat: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates an existing SCM integration.
+
+        Use this method to:
+
+        - Modify integration settings
+        - Update credentials
+        - Change configuration
+
+        ### Examples
+
+        - Update integration:
+
+          Updates OAuth credentials.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          oauthClientId: "new_client_id"
+          oauthPlaintextClientSecret: "new_client_secret"
+          ```
+
+        Args:
+          issuer_url: issuer_url can be set to override the authentication provider URL, if it doesn't
+              match the SCM host.
+
+          oauth_client_id: oauth_client_id can be set to update the OAuth app's client ID. If an empty
+              string is set, the OAuth configuration will be removed (regardless of whether a
+              client secret is set), and any existing Host Authentication Tokens for the SCM
+              integration's runner and host that were created using the OAuth app will be
+              deleted. This might lead to users being unable to access their repositories
+              until they re-authenticate.
+
+          oauth_plaintext_client_secret: oauth_plaintext_client_secret can be set to update the OAuth app's client
+              secret. The cleartext secret will be encrypted with the runner's public key
+              before being stored.
+
+          pat: pat can be set to enable or disable Personal Access Tokens support. When
+              disabling PATs, any existing Host Authentication Tokens for the SCM
+              integration's runner and host that were created using a PAT will be deleted.
+              This might lead to users being unable to access their repositories until they
+              re-authenticate.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/UpdateSCMIntegration",
+            body=await async_maybe_transform(
+                {
+                    "id": id,
+                    "issuer_url": issuer_url,
+                    "oauth_client_id": oauth_client_id,
+                    "oauth_plaintext_client_secret": oauth_plaintext_client_secret,
+                    "pat": pat,
+                },
+                scm_integration_update_params.ScmIntegrationUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: scm_integration_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: scm_integration_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[ScmIntegration, AsyncIntegrationsPage[ScmIntegration]]:
+        """
+        Lists SCM integrations for a runner.
+
+        Use this method to:
+
+        - View all integrations
+        - Monitor integration status
+        - Check available SCMs
+
+        ### Examples
+
+        - List integrations:
+
+          Shows all SCM integrations.
+
+          ```yaml
+          filter:
+            runnerIds: ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing scm integrations
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerConfigurationService/ListSCMIntegrations",
+            page=AsyncIntegrationsPage[ScmIntegration],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                scm_integration_list_params.ScmIntegrationListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    scm_integration_list_params.ScmIntegrationListParams,
+                ),
+            ),
+            model=ScmIntegration,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes an SCM integration.
+
+        Use this method to:
+
+        - Remove unused integrations
+        - Clean up configurations
+        - Revoke SCM access
+
+        ### Examples
+
+        - Delete integration:
+
+          Removes an SCM integration.
+
+          ```yaml
+          id: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerConfigurationService/DeleteSCMIntegration",
+            body=await async_maybe_transform({"id": id}, scm_integration_delete_params.ScmIntegrationDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class ScmIntegrationsResourceWithRawResponse:
+    def __init__(self, scm_integrations: ScmIntegrationsResource) -> None:
+        self._scm_integrations = scm_integrations
+
+        self.create = to_raw_response_wrapper(
+            scm_integrations.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            scm_integrations.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            scm_integrations.update,
+        )
+        self.list = to_raw_response_wrapper(
+            scm_integrations.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            scm_integrations.delete,
+        )
+
+
+class AsyncScmIntegrationsResourceWithRawResponse:
+    def __init__(self, scm_integrations: AsyncScmIntegrationsResource) -> None:
+        self._scm_integrations = scm_integrations
+
+        self.create = async_to_raw_response_wrapper(
+            scm_integrations.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            scm_integrations.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            scm_integrations.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            scm_integrations.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            scm_integrations.delete,
+        )
+
+
+class ScmIntegrationsResourceWithStreamingResponse:
+    def __init__(self, scm_integrations: ScmIntegrationsResource) -> None:
+        self._scm_integrations = scm_integrations
+
+        self.create = to_streamed_response_wrapper(
+            scm_integrations.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            scm_integrations.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            scm_integrations.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            scm_integrations.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            scm_integrations.delete,
+        )
+
+
+class AsyncScmIntegrationsResourceWithStreamingResponse:
+    def __init__(self, scm_integrations: AsyncScmIntegrationsResource) -> None:
+        self._scm_integrations = scm_integrations
+
+        self.create = async_to_streamed_response_wrapper(
+            scm_integrations.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            scm_integrations.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            scm_integrations.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            scm_integrations.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            scm_integrations.delete,
+        )
diff --git a/src/gitpod/resources/runners/policies.py b/src/gitpod/resources/runners/policies.py
new file mode 100644
index 0000000..4ce6cc3
--- /dev/null
+++ b/src/gitpod/resources/runners/policies.py
@@ -0,0 +1,667 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncPoliciesPage, AsyncPoliciesPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.runners import (
+    RunnerRole,
+    policy_list_params,
+    policy_create_params,
+    policy_delete_params,
+    policy_update_params,
+)
+from ...types.runners.runner_role import RunnerRole
+from ...types.runners.runner_policy import RunnerPolicy
+from ...types.runners.policy_create_response import PolicyCreateResponse
+from ...types.runners.policy_update_response import PolicyUpdateResponse
+
+__all__ = ["PoliciesResource", "AsyncPoliciesResource"]
+
+
+class PoliciesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> PoliciesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return PoliciesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> PoliciesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return PoliciesResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        role: RunnerRole | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyCreateResponse:
+        """
+        Creates a new policy for a runner.
+
+        Use this method to:
+
+        - Set up access controls
+        - Define group permissions
+        - Configure role-based access
+
+        ### Examples
+
+        - Create admin policy:
+
+          Grants admin access to a group.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: RUNNER_ROLE_ADMIN
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/CreateRunnerPolicy",
+            body=maybe_transform(
+                {
+                    "group_id": group_id,
+                    "role": role,
+                    "runner_id": runner_id,
+                },
+                policy_create_params.PolicyCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyCreateResponse,
+        )
+
+    def update(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        role: RunnerRole | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyUpdateResponse:
+        """
+        Updates an existing runner policy.
+
+        Use this method to:
+
+        - Modify access levels
+        - Change group roles
+        - Update permissions
+
+        ### Examples
+
+        - Update policy role:
+
+          Changes a group's access level.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: RUNNER_ROLE_USER
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/UpdateRunnerPolicy",
+            body=maybe_transform(
+                {
+                    "group_id": group_id,
+                    "role": role,
+                    "runner_id": runner_id,
+                },
+                policy_update_params.PolicyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyUpdateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: policy_list_params.Pagination | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncPoliciesPage[RunnerPolicy]:
+        """
+        Lists policies for a runner.
+
+        Use this method to:
+
+        - View access controls
+        - Check policy configurations
+        - Audit permissions
+
+        ### Examples
+
+        - List policies:
+
+          Shows all policies for a runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing project policies
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerService/ListRunnerPolicies",
+            page=SyncPoliciesPage[RunnerPolicy],
+            body=maybe_transform(
+                {
+                    "pagination": pagination,
+                    "runner_id": runner_id,
+                },
+                policy_list_params.PolicyListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    policy_list_params.PolicyListParams,
+                ),
+            ),
+            model=RunnerPolicy,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a runner policy.
+
+        Use this method to:
+
+        - Remove access controls
+        - Revoke permissions
+        - Clean up policies
+
+        ### Examples
+
+        - Delete policy:
+
+          Removes a group's access policy.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/DeleteRunnerPolicy",
+            body=maybe_transform(
+                {
+                    "group_id": group_id,
+                    "runner_id": runner_id,
+                },
+                policy_delete_params.PolicyDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncPoliciesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncPoliciesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncPoliciesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncPoliciesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncPoliciesResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        role: RunnerRole | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyCreateResponse:
+        """
+        Creates a new policy for a runner.
+
+        Use this method to:
+
+        - Set up access controls
+        - Define group permissions
+        - Configure role-based access
+
+        ### Examples
+
+        - Create admin policy:
+
+          Grants admin access to a group.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: RUNNER_ROLE_ADMIN
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/CreateRunnerPolicy",
+            body=await async_maybe_transform(
+                {
+                    "group_id": group_id,
+                    "role": role,
+                    "runner_id": runner_id,
+                },
+                policy_create_params.PolicyCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyCreateResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        role: RunnerRole | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PolicyUpdateResponse:
+        """
+        Updates an existing runner policy.
+
+        Use this method to:
+
+        - Modify access levels
+        - Change group roles
+        - Update permissions
+
+        ### Examples
+
+        - Update policy role:
+
+          Changes a group's access level.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          role: RUNNER_ROLE_USER
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/UpdateRunnerPolicy",
+            body=await async_maybe_transform(
+                {
+                    "group_id": group_id,
+                    "role": role,
+                    "runner_id": runner_id,
+                },
+                policy_update_params.PolicyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PolicyUpdateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        pagination: policy_list_params.Pagination | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[RunnerPolicy, AsyncPoliciesPage[RunnerPolicy]]:
+        """
+        Lists policies for a runner.
+
+        Use this method to:
+
+        - View access controls
+        - Check policy configurations
+        - Audit permissions
+
+        ### Examples
+
+        - List policies:
+
+          Shows all policies for a runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing project policies
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerService/ListRunnerPolicies",
+            page=AsyncPoliciesPage[RunnerPolicy],
+            body=maybe_transform(
+                {
+                    "pagination": pagination,
+                    "runner_id": runner_id,
+                },
+                policy_list_params.PolicyListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    policy_list_params.PolicyListParams,
+                ),
+            ),
+            model=RunnerPolicy,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        group_id: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a runner policy.
+
+        Use this method to:
+
+        - Remove access controls
+        - Revoke permissions
+        - Clean up policies
+
+        ### Examples
+
+        - Delete policy:
+
+          Removes a group's access policy.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          groupId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          ```
+
+        Args:
+          group_id: group_id specifies the group_id identifier
+
+          runner_id: runner_id specifies the project identifier
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/DeleteRunnerPolicy",
+            body=await async_maybe_transform(
+                {
+                    "group_id": group_id,
+                    "runner_id": runner_id,
+                },
+                policy_delete_params.PolicyDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class PoliciesResourceWithRawResponse:
+    def __init__(self, policies: PoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = to_raw_response_wrapper(
+            policies.create,
+        )
+        self.update = to_raw_response_wrapper(
+            policies.update,
+        )
+        self.list = to_raw_response_wrapper(
+            policies.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            policies.delete,
+        )
+
+
+class AsyncPoliciesResourceWithRawResponse:
+    def __init__(self, policies: AsyncPoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = async_to_raw_response_wrapper(
+            policies.create,
+        )
+        self.update = async_to_raw_response_wrapper(
+            policies.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            policies.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            policies.delete,
+        )
+
+
+class PoliciesResourceWithStreamingResponse:
+    def __init__(self, policies: PoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = to_streamed_response_wrapper(
+            policies.create,
+        )
+        self.update = to_streamed_response_wrapper(
+            policies.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            policies.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            policies.delete,
+        )
+
+
+class AsyncPoliciesResourceWithStreamingResponse:
+    def __init__(self, policies: AsyncPoliciesResource) -> None:
+        self._policies = policies
+
+        self.create = async_to_streamed_response_wrapper(
+            policies.create,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            policies.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            policies.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            policies.delete,
+        )
diff --git a/src/gitpod/resources/runners/runners.py b/src/gitpod/resources/runners/runners.py
new file mode 100644
index 0000000..876c529
--- /dev/null
+++ b/src/gitpod/resources/runners/runners.py
@@ -0,0 +1,1327 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...types import (
+    RunnerKind,
+    RunnerProvider,
+    runner_list_params,
+    runner_create_params,
+    runner_delete_params,
+    runner_update_params,
+    runner_retrieve_params,
+    runner_parse_context_url_params,
+    runner_create_runner_token_params,
+    runner_check_authentication_for_host_params,
+)
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from .policies import (
+    PoliciesResource,
+    AsyncPoliciesResource,
+    PoliciesResourceWithRawResponse,
+    AsyncPoliciesResourceWithRawResponse,
+    PoliciesResourceWithStreamingResponse,
+    AsyncPoliciesResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncRunnersPage, AsyncRunnersPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.runner import Runner
+from ...types.runner_kind import RunnerKind
+from ...types.runner_provider import RunnerProvider
+from ...types.runner_spec_param import RunnerSpecParam
+from .configurations.configurations import (
+    ConfigurationsResource,
+    AsyncConfigurationsResource,
+    ConfigurationsResourceWithRawResponse,
+    AsyncConfigurationsResourceWithRawResponse,
+    ConfigurationsResourceWithStreamingResponse,
+    AsyncConfigurationsResourceWithStreamingResponse,
+)
+from ...types.runner_create_response import RunnerCreateResponse
+from ...types.runner_retrieve_response import RunnerRetrieveResponse
+from ...types.runner_parse_context_url_response import RunnerParseContextURLResponse
+from ...types.runner_create_runner_token_response import RunnerCreateRunnerTokenResponse
+from ...types.runner_check_authentication_for_host_response import RunnerCheckAuthenticationForHostResponse
+
+__all__ = ["RunnersResource", "AsyncRunnersResource"]
+
+
+class RunnersResource(SyncAPIResource):
+    @cached_property
+    def configurations(self) -> ConfigurationsResource:
+        return ConfigurationsResource(self._client)
+
+    @cached_property
+    def policies(self) -> PoliciesResource:
+        return PoliciesResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> RunnersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return RunnersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> RunnersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return RunnersResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        kind: RunnerKind | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        provider: RunnerProvider | NotGiven = NOT_GIVEN,
+        runner_manager_id: str | NotGiven = NOT_GIVEN,
+        spec: RunnerSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerCreateResponse:
+        """Creates a new runner registration with the server.
+
+        Registrations are very
+        short-lived and must be renewed every 30 seconds.
+
+        Use this method to:
+
+        - Register organization runners
+        - Set up runner configurations
+        - Initialize runner credentials
+        - Configure auto-updates
+
+        ### Examples
+
+        - Create cloud runner:
+
+          Creates a new runner in AWS EC2.
+
+          ```yaml
+          name: "Production Runner"
+          provider: RUNNER_PROVIDER_AWS_EC2
+          spec:
+            desiredPhase: RUNNER_PHASE_ACTIVE
+            configuration:
+              region: "us-west"
+              releaseChannel: RUNNER_RELEASE_CHANNEL_STABLE
+              autoUpdate: true
+          ```
+
+        - Create local runner:
+
+          Creates a new local runner on Linux.
+
+          ```yaml
+          name: "Local Development Runner"
+          provider: RUNNER_PROVIDER_LINUX_HOST
+          spec:
+            desiredPhase: RUNNER_PHASE_ACTIVE
+            configuration:
+              releaseChannel: RUNNER_RELEASE_CHANNEL_LATEST
+              autoUpdate: true
+          ```
+
+        Args:
+          kind: The runner's kind This field is optional and here for backwards-compatibility.
+              Use the provider field instead. If provider is set, the runner's kind will be
+              deduced from the provider. Only one of kind and provider must be set.
+
+          name: The runner name for humans
+
+          provider: The specific implementation type of the runner This field is optional for
+              backwards compatibility but will be required in the future. When specified, kind
+              must not be specified (will be deduced from provider)
+
+          runner_manager_id: The runner manager id specifies the runner manager for the managed runner. This
+              field is mandatory for managed runners, otheriwse should not be set.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/CreateRunner",
+            body=maybe_transform(
+                {
+                    "kind": kind,
+                    "name": name,
+                    "provider": provider,
+                    "runner_manager_id": runner_manager_id,
+                    "spec": spec,
+                },
+                runner_create_params.RunnerCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        *,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerRetrieveResponse:
+        """
+        Gets details about a specific runner.
+
+        Use this method to:
+
+        - Check runner status
+        - View runner configuration
+        - Monitor runner health
+        - Verify runner capabilities
+
+        ### Examples
+
+        - Get runner details:
+
+          Retrieves information about a specific runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/GetRunner",
+            body=maybe_transform({"runner_id": runner_id}, runner_retrieve_params.RunnerRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerRetrieveResponse,
+        )
+
+    def update(
+        self,
+        *,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        spec: Optional[runner_update_params.Spec] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates a runner's configuration.
+
+        Use this method to:
+
+        - Modify runner settings
+        - Update release channels
+        - Change runner status
+        - Configure auto-update settings
+
+        ### Examples
+
+        - Update configuration:
+
+          Changes runner settings.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          name: "Updated Runner Name"
+          spec:
+            configuration:
+              releaseChannel: RUNNER_RELEASE_CHANNEL_LATEST
+              autoUpdate: true
+          ```
+
+        Args:
+          name: The runner's name which is shown to users
+
+          runner_id: runner_id specifies which runner to be updated.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/UpdateRunner",
+            body=maybe_transform(
+                {
+                    "name": name,
+                    "runner_id": runner_id,
+                    "spec": spec,
+                },
+                runner_update_params.RunnerUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: runner_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: runner_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncRunnersPage[Runner]:
+        """
+        Lists all registered runners with optional filtering.
+
+        Use this method to:
+
+        - View all available runners
+        - Filter by runner type
+        - Monitor runner status
+        - Check runner availability
+
+        ### Examples
+
+        - List all runners:
+
+          Shows all runners with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by provider:
+
+          Lists only AWS EC2 runners.
+
+          ```yaml
+          filter:
+            providers: ["RUNNER_PROVIDER_AWS_EC2"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing runners
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerService/ListRunners",
+            page=SyncRunnersPage[Runner],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                runner_list_params.RunnerListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    runner_list_params.RunnerListParams,
+                ),
+            ),
+            model=Runner,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        force: bool | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a runner permanently.
+
+        Use this method to:
+
+        - Remove unused runners
+        - Clean up runner registrations
+        - Delete obsolete runners
+
+        ### Examples
+
+        - Delete runner:
+
+          Permanently removes a runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          force: force indicates whether the runner should be deleted forcefully. When force
+              deleting a Runner, all Environments on the runner are also force deleted and
+              regular Runner lifecycle is not respected. Force deleting can result in data
+              loss.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/DeleteRunner",
+            body=maybe_transform(
+                {
+                    "force": force,
+                    "runner_id": runner_id,
+                },
+                runner_delete_params.RunnerDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def check_authentication_for_host(
+        self,
+        *,
+        host: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerCheckAuthenticationForHostResponse:
+        """
+        Checks if a user is authenticated for a specific host.
+
+        Use this method to:
+
+        - Verify authentication status
+        - Get authentication URLs
+        - Check PAT support
+
+        ### Examples
+
+        - Check authentication:
+
+          Verifies authentication for a host.
+
+          ```yaml
+          host: "github.com"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/CheckAuthenticationForHost",
+            body=maybe_transform(
+                {
+                    "host": host,
+                    "runner_id": runner_id,
+                },
+                runner_check_authentication_for_host_params.RunnerCheckAuthenticationForHostParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerCheckAuthenticationForHostResponse,
+        )
+
+    def create_runner_token(
+        self,
+        *,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerCreateRunnerTokenResponse:
+        """
+        Creates a new authentication token for a runner.
+
+        Use this method to:
+
+        - Generate runner credentials
+        - Renew expired tokens
+        - Set up runner authentication
+
+        Note: This does not expire previously issued tokens.
+
+        ### Examples
+
+        - Create token:
+
+          Creates a new token for runner authentication.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/CreateRunnerToken",
+            body=maybe_transform(
+                {"runner_id": runner_id}, runner_create_runner_token_params.RunnerCreateRunnerTokenParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerCreateRunnerTokenResponse,
+        )
+
+    def parse_context_url(
+        self,
+        *,
+        context_url: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerParseContextURLResponse:
+        """
+        Parses a context URL and returns the parsed result.
+
+        Use this method to:
+
+        - Validate context URLs
+        - Check repository access
+        - Verify branch existence
+
+        Returns:
+
+        - FAILED_PRECONDITION if authentication is required
+        - PERMISSION_DENIED if access is not allowed
+        - INVALID_ARGUMENT if URL is invalid
+        - NOT_FOUND if repository/branch doesn't exist
+
+        ### Examples
+
+        - Parse URL:
+
+          Parses and validates a context URL.
+
+          ```yaml
+          contextUrl: "https://github.com/org/repo/tree/main"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.RunnerService/ParseContextURL",
+            body=maybe_transform(
+                {
+                    "context_url": context_url,
+                    "runner_id": runner_id,
+                },
+                runner_parse_context_url_params.RunnerParseContextURLParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerParseContextURLResponse,
+        )
+
+
+class AsyncRunnersResource(AsyncAPIResource):
+    @cached_property
+    def configurations(self) -> AsyncConfigurationsResource:
+        return AsyncConfigurationsResource(self._client)
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResource:
+        return AsyncPoliciesResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncRunnersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncRunnersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncRunnersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncRunnersResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        kind: RunnerKind | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        provider: RunnerProvider | NotGiven = NOT_GIVEN,
+        runner_manager_id: str | NotGiven = NOT_GIVEN,
+        spec: RunnerSpecParam | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerCreateResponse:
+        """Creates a new runner registration with the server.
+
+        Registrations are very
+        short-lived and must be renewed every 30 seconds.
+
+        Use this method to:
+
+        - Register organization runners
+        - Set up runner configurations
+        - Initialize runner credentials
+        - Configure auto-updates
+
+        ### Examples
+
+        - Create cloud runner:
+
+          Creates a new runner in AWS EC2.
+
+          ```yaml
+          name: "Production Runner"
+          provider: RUNNER_PROVIDER_AWS_EC2
+          spec:
+            desiredPhase: RUNNER_PHASE_ACTIVE
+            configuration:
+              region: "us-west"
+              releaseChannel: RUNNER_RELEASE_CHANNEL_STABLE
+              autoUpdate: true
+          ```
+
+        - Create local runner:
+
+          Creates a new local runner on Linux.
+
+          ```yaml
+          name: "Local Development Runner"
+          provider: RUNNER_PROVIDER_LINUX_HOST
+          spec:
+            desiredPhase: RUNNER_PHASE_ACTIVE
+            configuration:
+              releaseChannel: RUNNER_RELEASE_CHANNEL_LATEST
+              autoUpdate: true
+          ```
+
+        Args:
+          kind: The runner's kind This field is optional and here for backwards-compatibility.
+              Use the provider field instead. If provider is set, the runner's kind will be
+              deduced from the provider. Only one of kind and provider must be set.
+
+          name: The runner name for humans
+
+          provider: The specific implementation type of the runner This field is optional for
+              backwards compatibility but will be required in the future. When specified, kind
+              must not be specified (will be deduced from provider)
+
+          runner_manager_id: The runner manager id specifies the runner manager for the managed runner. This
+              field is mandatory for managed runners, otheriwse should not be set.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/CreateRunner",
+            body=await async_maybe_transform(
+                {
+                    "kind": kind,
+                    "name": name,
+                    "provider": provider,
+                    "runner_manager_id": runner_manager_id,
+                    "spec": spec,
+                },
+                runner_create_params.RunnerCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        *,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerRetrieveResponse:
+        """
+        Gets details about a specific runner.
+
+        Use this method to:
+
+        - Check runner status
+        - View runner configuration
+        - Monitor runner health
+        - Verify runner capabilities
+
+        ### Examples
+
+        - Get runner details:
+
+          Retrieves information about a specific runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/GetRunner",
+            body=await async_maybe_transform({"runner_id": runner_id}, runner_retrieve_params.RunnerRetrieveParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerRetrieveResponse,
+        )
+
+    async def update(
+        self,
+        *,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        spec: Optional[runner_update_params.Spec] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates a runner's configuration.
+
+        Use this method to:
+
+        - Modify runner settings
+        - Update release channels
+        - Change runner status
+        - Configure auto-update settings
+
+        ### Examples
+
+        - Update configuration:
+
+          Changes runner settings.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          name: "Updated Runner Name"
+          spec:
+            configuration:
+              releaseChannel: RUNNER_RELEASE_CHANNEL_LATEST
+              autoUpdate: true
+          ```
+
+        Args:
+          name: The runner's name which is shown to users
+
+          runner_id: runner_id specifies which runner to be updated.
+
+              +required
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/UpdateRunner",
+            body=await async_maybe_transform(
+                {
+                    "name": name,
+                    "runner_id": runner_id,
+                    "spec": spec,
+                },
+                runner_update_params.RunnerUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: runner_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: runner_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Runner, AsyncRunnersPage[Runner]]:
+        """
+        Lists all registered runners with optional filtering.
+
+        Use this method to:
+
+        - View all available runners
+        - Filter by runner type
+        - Monitor runner status
+        - Check runner availability
+
+        ### Examples
+
+        - List all runners:
+
+          Shows all runners with pagination.
+
+          ```yaml
+          pagination:
+            pageSize: 20
+          ```
+
+        - Filter by provider:
+
+          Lists only AWS EC2 runners.
+
+          ```yaml
+          filter:
+            providers: ["RUNNER_PROVIDER_AWS_EC2"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing runners
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.RunnerService/ListRunners",
+            page=AsyncRunnersPage[Runner],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                runner_list_params.RunnerListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    runner_list_params.RunnerListParams,
+                ),
+            ),
+            model=Runner,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        force: bool | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a runner permanently.
+
+        Use this method to:
+
+        - Remove unused runners
+        - Clean up runner registrations
+        - Delete obsolete runners
+
+        ### Examples
+
+        - Delete runner:
+
+          Permanently removes a runner.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          force: force indicates whether the runner should be deleted forcefully. When force
+              deleting a Runner, all Environments on the runner are also force deleted and
+              regular Runner lifecycle is not respected. Force deleting can result in data
+              loss.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/DeleteRunner",
+            body=await async_maybe_transform(
+                {
+                    "force": force,
+                    "runner_id": runner_id,
+                },
+                runner_delete_params.RunnerDeleteParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def check_authentication_for_host(
+        self,
+        *,
+        host: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerCheckAuthenticationForHostResponse:
+        """
+        Checks if a user is authenticated for a specific host.
+
+        Use this method to:
+
+        - Verify authentication status
+        - Get authentication URLs
+        - Check PAT support
+
+        ### Examples
+
+        - Check authentication:
+
+          Verifies authentication for a host.
+
+          ```yaml
+          host: "github.com"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/CheckAuthenticationForHost",
+            body=await async_maybe_transform(
+                {
+                    "host": host,
+                    "runner_id": runner_id,
+                },
+                runner_check_authentication_for_host_params.RunnerCheckAuthenticationForHostParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerCheckAuthenticationForHostResponse,
+        )
+
+    async def create_runner_token(
+        self,
+        *,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerCreateRunnerTokenResponse:
+        """
+        Creates a new authentication token for a runner.
+
+        Use this method to:
+
+        - Generate runner credentials
+        - Renew expired tokens
+        - Set up runner authentication
+
+        Note: This does not expire previously issued tokens.
+
+        ### Examples
+
+        - Create token:
+
+          Creates a new token for runner authentication.
+
+          ```yaml
+          runnerId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/CreateRunnerToken",
+            body=await async_maybe_transform(
+                {"runner_id": runner_id}, runner_create_runner_token_params.RunnerCreateRunnerTokenParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerCreateRunnerTokenResponse,
+        )
+
+    async def parse_context_url(
+        self,
+        *,
+        context_url: str | NotGiven = NOT_GIVEN,
+        runner_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RunnerParseContextURLResponse:
+        """
+        Parses a context URL and returns the parsed result.
+
+        Use this method to:
+
+        - Validate context URLs
+        - Check repository access
+        - Verify branch existence
+
+        Returns:
+
+        - FAILED_PRECONDITION if authentication is required
+        - PERMISSION_DENIED if access is not allowed
+        - INVALID_ARGUMENT if URL is invalid
+        - NOT_FOUND if repository/branch doesn't exist
+
+        ### Examples
+
+        - Parse URL:
+
+          Parses and validates a context URL.
+
+          ```yaml
+          contextUrl: "https://github.com/org/repo/tree/main"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.RunnerService/ParseContextURL",
+            body=await async_maybe_transform(
+                {
+                    "context_url": context_url,
+                    "runner_id": runner_id,
+                },
+                runner_parse_context_url_params.RunnerParseContextURLParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RunnerParseContextURLResponse,
+        )
+
+
+class RunnersResourceWithRawResponse:
+    def __init__(self, runners: RunnersResource) -> None:
+        self._runners = runners
+
+        self.create = to_raw_response_wrapper(
+            runners.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            runners.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            runners.update,
+        )
+        self.list = to_raw_response_wrapper(
+            runners.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            runners.delete,
+        )
+        self.check_authentication_for_host = to_raw_response_wrapper(
+            runners.check_authentication_for_host,
+        )
+        self.create_runner_token = to_raw_response_wrapper(
+            runners.create_runner_token,
+        )
+        self.parse_context_url = to_raw_response_wrapper(
+            runners.parse_context_url,
+        )
+
+    @cached_property
+    def configurations(self) -> ConfigurationsResourceWithRawResponse:
+        return ConfigurationsResourceWithRawResponse(self._runners.configurations)
+
+    @cached_property
+    def policies(self) -> PoliciesResourceWithRawResponse:
+        return PoliciesResourceWithRawResponse(self._runners.policies)
+
+
+class AsyncRunnersResourceWithRawResponse:
+    def __init__(self, runners: AsyncRunnersResource) -> None:
+        self._runners = runners
+
+        self.create = async_to_raw_response_wrapper(
+            runners.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            runners.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            runners.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            runners.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            runners.delete,
+        )
+        self.check_authentication_for_host = async_to_raw_response_wrapper(
+            runners.check_authentication_for_host,
+        )
+        self.create_runner_token = async_to_raw_response_wrapper(
+            runners.create_runner_token,
+        )
+        self.parse_context_url = async_to_raw_response_wrapper(
+            runners.parse_context_url,
+        )
+
+    @cached_property
+    def configurations(self) -> AsyncConfigurationsResourceWithRawResponse:
+        return AsyncConfigurationsResourceWithRawResponse(self._runners.configurations)
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResourceWithRawResponse:
+        return AsyncPoliciesResourceWithRawResponse(self._runners.policies)
+
+
+class RunnersResourceWithStreamingResponse:
+    def __init__(self, runners: RunnersResource) -> None:
+        self._runners = runners
+
+        self.create = to_streamed_response_wrapper(
+            runners.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            runners.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            runners.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            runners.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            runners.delete,
+        )
+        self.check_authentication_for_host = to_streamed_response_wrapper(
+            runners.check_authentication_for_host,
+        )
+        self.create_runner_token = to_streamed_response_wrapper(
+            runners.create_runner_token,
+        )
+        self.parse_context_url = to_streamed_response_wrapper(
+            runners.parse_context_url,
+        )
+
+    @cached_property
+    def configurations(self) -> ConfigurationsResourceWithStreamingResponse:
+        return ConfigurationsResourceWithStreamingResponse(self._runners.configurations)
+
+    @cached_property
+    def policies(self) -> PoliciesResourceWithStreamingResponse:
+        return PoliciesResourceWithStreamingResponse(self._runners.policies)
+
+
+class AsyncRunnersResourceWithStreamingResponse:
+    def __init__(self, runners: AsyncRunnersResource) -> None:
+        self._runners = runners
+
+        self.create = async_to_streamed_response_wrapper(
+            runners.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            runners.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            runners.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            runners.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            runners.delete,
+        )
+        self.check_authentication_for_host = async_to_streamed_response_wrapper(
+            runners.check_authentication_for_host,
+        )
+        self.create_runner_token = async_to_streamed_response_wrapper(
+            runners.create_runner_token,
+        )
+        self.parse_context_url = async_to_streamed_response_wrapper(
+            runners.parse_context_url,
+        )
+
+    @cached_property
+    def configurations(self) -> AsyncConfigurationsResourceWithStreamingResponse:
+        return AsyncConfigurationsResourceWithStreamingResponse(self._runners.configurations)
+
+    @cached_property
+    def policies(self) -> AsyncPoliciesResourceWithStreamingResponse:
+        return AsyncPoliciesResourceWithStreamingResponse(self._runners.policies)
diff --git a/src/gitpod/resources/secrets.py b/src/gitpod/resources/secrets.py
new file mode 100644
index 0000000..42c8485
--- /dev/null
+++ b/src/gitpod/resources/secrets.py
@@ -0,0 +1,855 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..types import (
+    secret_list_params,
+    secret_create_params,
+    secret_delete_params,
+    secret_get_value_params,
+    secret_update_value_params,
+)
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncSecretsPage, AsyncSecretsPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.secret import Secret
+from ..types.secret_scope_param import SecretScopeParam
+from ..types.secret_create_response import SecretCreateResponse
+from ..types.secret_get_value_response import SecretGetValueResponse
+
+__all__ = ["SecretsResource", "AsyncSecretsResource"]
+
+
+class SecretsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> SecretsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return SecretsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> SecretsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return SecretsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        container_registry_basic_auth_host: str | NotGiven = NOT_GIVEN,
+        environment_variable: bool | NotGiven = NOT_GIVEN,
+        file_path: str | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        scope: SecretScopeParam | NotGiven = NOT_GIVEN,
+        value: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SecretCreateResponse:
+        """
+        Creates a new secret for a project.
+
+        Use this method to:
+
+        - Store sensitive configuration values
+        - Set up environment variables
+        - Configure registry authentication
+        - Add file-based secrets
+
+        ### Examples
+
+        - Create environment variable:
+
+          Creates a secret that will be available as an environment variable.
+
+          ```yaml
+          name: "DATABASE_URL"
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          value: "postgresql://user:pass@localhost:5432/db"
+          environmentVariable: true
+          ```
+
+        - Create file secret:
+
+          Creates a secret that will be mounted as a file.
+
+          ```yaml
+          name: "SSH_KEY"
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          value: "-----BEGIN RSA PRIVATE KEY-----\n..."
+          filePath: "/home/gitpod/.ssh/id_rsa"
+          ```
+
+        - Create registry auth:
+
+          Creates credentials for private container registry.
+
+          ```yaml
+          name: "DOCKER_AUTH"
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          value: "username:password"
+          containerRegistryBasicAuthHost: "https://registry.example.com"
+          ```
+
+        Args:
+          container_registry_basic_auth_host: secret will be mounted as a docker config in the environment VM, mount will have
+              the docker registry host
+
+          environment_variable: secret will be created as an Environment Variable with the same name as the
+              secret
+
+          file_path: absolute path to the file where the secret is mounted value must be an absolute
+              path (start with a /):
+
+              ```
+              this.matches("^/(?:[^/]*/)*.*$")
+              ```
+
+          project_id: project_id is the ProjectID this Secret belongs to Deprecated: use scope instead
+
+          scope: scope is the scope of the secret
+
+          value: value is the plaintext value of the secret
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.SecretService/CreateSecret",
+            body=maybe_transform(
+                {
+                    "container_registry_basic_auth_host": container_registry_basic_auth_host,
+                    "environment_variable": environment_variable,
+                    "file_path": file_path,
+                    "name": name,
+                    "project_id": project_id,
+                    "scope": scope,
+                    "value": value,
+                },
+                secret_create_params.SecretCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SecretCreateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: secret_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: secret_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncSecretsPage[Secret]:
+        """
+        Lists secrets
+
+        Use this method to:
+
+        - View all project secrets
+        - View all user secrets
+
+        ### Examples
+
+        - List project secrets:
+
+          Shows all secrets for a project.
+
+          ```yaml
+          filter:
+            scope:
+              projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List user secrets:
+
+          Shows all secrets for a user.
+
+          ```yaml
+          filter:
+            scope:
+              userId: "123e4567-e89b-12d3-a456-426614174000"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.SecretService/ListSecrets",
+            page=SyncSecretsPage[Secret],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                secret_list_params.SecretListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    secret_list_params.SecretListParams,
+                ),
+            ),
+            model=Secret,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        secret_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a secret permanently.
+
+        Use this method to:
+
+        - Remove unused secrets
+        - Clean up old credentials
+
+        ### Examples
+
+        - Delete secret:
+
+          Permanently removes a secret.
+
+          ```yaml
+          secretId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.SecretService/DeleteSecret",
+            body=maybe_transform({"secret_id": secret_id}, secret_delete_params.SecretDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def get_value(
+        self,
+        *,
+        secret_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SecretGetValueResponse:
+        """Gets the value of a secret.
+
+        Only available to environments that are authorized
+        to access the secret.
+
+        Use this method to:
+
+        - Retrieve secret values
+        - Access credentials
+
+        ### Examples
+
+        - Get secret value:
+
+          Retrieves the value of a specific secret.
+
+          ```yaml
+          secretId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.SecretService/GetSecretValue",
+            body=maybe_transform({"secret_id": secret_id}, secret_get_value_params.SecretGetValueParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SecretGetValueResponse,
+        )
+
+    def update_value(
+        self,
+        *,
+        secret_id: str | NotGiven = NOT_GIVEN,
+        value: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates the value of an existing secret.
+
+        Use this method to:
+
+        - Rotate secret values
+        - Update credentials
+
+        ### Examples
+
+        - Update secret value:
+
+          Changes the value of an existing secret.
+
+          ```yaml
+          secretId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          value: "new-secret-value"
+          ```
+
+        Args:
+          value: value is the plaintext value of the secret
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.SecretService/UpdateSecretValue",
+            body=maybe_transform(
+                {
+                    "secret_id": secret_id,
+                    "value": value,
+                },
+                secret_update_value_params.SecretUpdateValueParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncSecretsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncSecretsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncSecretsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncSecretsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncSecretsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        container_registry_basic_auth_host: str | NotGiven = NOT_GIVEN,
+        environment_variable: bool | NotGiven = NOT_GIVEN,
+        file_path: str | NotGiven = NOT_GIVEN,
+        name: str | NotGiven = NOT_GIVEN,
+        project_id: str | NotGiven = NOT_GIVEN,
+        scope: SecretScopeParam | NotGiven = NOT_GIVEN,
+        value: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SecretCreateResponse:
+        """
+        Creates a new secret for a project.
+
+        Use this method to:
+
+        - Store sensitive configuration values
+        - Set up environment variables
+        - Configure registry authentication
+        - Add file-based secrets
+
+        ### Examples
+
+        - Create environment variable:
+
+          Creates a secret that will be available as an environment variable.
+
+          ```yaml
+          name: "DATABASE_URL"
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          value: "postgresql://user:pass@localhost:5432/db"
+          environmentVariable: true
+          ```
+
+        - Create file secret:
+
+          Creates a secret that will be mounted as a file.
+
+          ```yaml
+          name: "SSH_KEY"
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          value: "-----BEGIN RSA PRIVATE KEY-----\n..."
+          filePath: "/home/gitpod/.ssh/id_rsa"
+          ```
+
+        - Create registry auth:
+
+          Creates credentials for private container registry.
+
+          ```yaml
+          name: "DOCKER_AUTH"
+          projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          value: "username:password"
+          containerRegistryBasicAuthHost: "https://registry.example.com"
+          ```
+
+        Args:
+          container_registry_basic_auth_host: secret will be mounted as a docker config in the environment VM, mount will have
+              the docker registry host
+
+          environment_variable: secret will be created as an Environment Variable with the same name as the
+              secret
+
+          file_path: absolute path to the file where the secret is mounted value must be an absolute
+              path (start with a /):
+
+              ```
+              this.matches("^/(?:[^/]*/)*.*$")
+              ```
+
+          project_id: project_id is the ProjectID this Secret belongs to Deprecated: use scope instead
+
+          scope: scope is the scope of the secret
+
+          value: value is the plaintext value of the secret
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.SecretService/CreateSecret",
+            body=await async_maybe_transform(
+                {
+                    "container_registry_basic_auth_host": container_registry_basic_auth_host,
+                    "environment_variable": environment_variable,
+                    "file_path": file_path,
+                    "name": name,
+                    "project_id": project_id,
+                    "scope": scope,
+                    "value": value,
+                },
+                secret_create_params.SecretCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SecretCreateResponse,
+        )
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: secret_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: secret_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[Secret, AsyncSecretsPage[Secret]]:
+        """
+        Lists secrets
+
+        Use this method to:
+
+        - View all project secrets
+        - View all user secrets
+
+        ### Examples
+
+        - List project secrets:
+
+          Shows all secrets for a project.
+
+          ```yaml
+          filter:
+            scope:
+              projectId: "b0e12f6c-4c67-429d-a4a6-d9838b5da047"
+          pagination:
+            pageSize: 20
+          ```
+
+        - List user secrets:
+
+          Shows all secrets for a user.
+
+          ```yaml
+          filter:
+            scope:
+              userId: "123e4567-e89b-12d3-a456-426614174000"
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          pagination: pagination contains the pagination options for listing environments
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.SecretService/ListSecrets",
+            page=AsyncSecretsPage[Secret],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                secret_list_params.SecretListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    secret_list_params.SecretListParams,
+                ),
+            ),
+            model=Secret,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        secret_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a secret permanently.
+
+        Use this method to:
+
+        - Remove unused secrets
+        - Clean up old credentials
+
+        ### Examples
+
+        - Delete secret:
+
+          Permanently removes a secret.
+
+          ```yaml
+          secretId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.SecretService/DeleteSecret",
+            body=await async_maybe_transform({"secret_id": secret_id}, secret_delete_params.SecretDeleteParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def get_value(
+        self,
+        *,
+        secret_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SecretGetValueResponse:
+        """Gets the value of a secret.
+
+        Only available to environments that are authorized
+        to access the secret.
+
+        Use this method to:
+
+        - Retrieve secret values
+        - Access credentials
+
+        ### Examples
+
+        - Get secret value:
+
+          Retrieves the value of a specific secret.
+
+          ```yaml
+          secretId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.SecretService/GetSecretValue",
+            body=await async_maybe_transform({"secret_id": secret_id}, secret_get_value_params.SecretGetValueParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=SecretGetValueResponse,
+        )
+
+    async def update_value(
+        self,
+        *,
+        secret_id: str | NotGiven = NOT_GIVEN,
+        value: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Updates the value of an existing secret.
+
+        Use this method to:
+
+        - Rotate secret values
+        - Update credentials
+
+        ### Examples
+
+        - Update secret value:
+
+          Changes the value of an existing secret.
+
+          ```yaml
+          secretId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          value: "new-secret-value"
+          ```
+
+        Args:
+          value: value is the plaintext value of the secret
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.SecretService/UpdateSecretValue",
+            body=await async_maybe_transform(
+                {
+                    "secret_id": secret_id,
+                    "value": value,
+                },
+                secret_update_value_params.SecretUpdateValueParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class SecretsResourceWithRawResponse:
+    def __init__(self, secrets: SecretsResource) -> None:
+        self._secrets = secrets
+
+        self.create = to_raw_response_wrapper(
+            secrets.create,
+        )
+        self.list = to_raw_response_wrapper(
+            secrets.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            secrets.delete,
+        )
+        self.get_value = to_raw_response_wrapper(
+            secrets.get_value,
+        )
+        self.update_value = to_raw_response_wrapper(
+            secrets.update_value,
+        )
+
+
+class AsyncSecretsResourceWithRawResponse:
+    def __init__(self, secrets: AsyncSecretsResource) -> None:
+        self._secrets = secrets
+
+        self.create = async_to_raw_response_wrapper(
+            secrets.create,
+        )
+        self.list = async_to_raw_response_wrapper(
+            secrets.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            secrets.delete,
+        )
+        self.get_value = async_to_raw_response_wrapper(
+            secrets.get_value,
+        )
+        self.update_value = async_to_raw_response_wrapper(
+            secrets.update_value,
+        )
+
+
+class SecretsResourceWithStreamingResponse:
+    def __init__(self, secrets: SecretsResource) -> None:
+        self._secrets = secrets
+
+        self.create = to_streamed_response_wrapper(
+            secrets.create,
+        )
+        self.list = to_streamed_response_wrapper(
+            secrets.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            secrets.delete,
+        )
+        self.get_value = to_streamed_response_wrapper(
+            secrets.get_value,
+        )
+        self.update_value = to_streamed_response_wrapper(
+            secrets.update_value,
+        )
+
+
+class AsyncSecretsResourceWithStreamingResponse:
+    def __init__(self, secrets: AsyncSecretsResource) -> None:
+        self._secrets = secrets
+
+        self.create = async_to_streamed_response_wrapper(
+            secrets.create,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            secrets.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            secrets.delete,
+        )
+        self.get_value = async_to_streamed_response_wrapper(
+            secrets.get_value,
+        )
+        self.update_value = async_to_streamed_response_wrapper(
+            secrets.update_value,
+        )
diff --git a/src/gitpod/resources/usage.py b/src/gitpod/resources/usage.py
new file mode 100644
index 0000000..71c698e
--- /dev/null
+++ b/src/gitpod/resources/usage.py
@@ -0,0 +1,258 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..types import usage_list_environment_runtime_records_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncRecordsPage, AsyncRecordsPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.environment_usage_record import EnvironmentUsageRecord
+
+__all__ = ["UsageResource", "AsyncUsageResource"]
+
+
+class UsageResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> UsageResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return UsageResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> UsageResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return UsageResourceWithStreamingResponse(self)
+
+    def list_environment_runtime_records(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: usage_list_environment_runtime_records_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: usage_list_environment_runtime_records_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncRecordsPage[EnvironmentUsageRecord]:
+        """
+        Lists completed environment runtime records within a specified date range.
+
+        Returns a list of environment runtime records that were completed within the
+        specified date range. Records of currently running environments are not
+        included.
+
+        Use this method to:
+
+        - View environment runtime records
+        - Filter by project
+        - Create custom usage reports
+
+        ### Example
+
+        ```yaml
+        filter:
+          projectId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          dateRange:
+            startTime: "2024-01-01T00:00:00Z"
+            endTime: "2024-01-02T00:00:00Z"
+        pagination:
+          pageSize: 100
+        ```
+
+        Args:
+          filter: Filter options.
+
+          pagination: Pagination options.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.UsageService/ListEnvironmentUsageRecords",
+            page=SyncRecordsPage[EnvironmentUsageRecord],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                usage_list_environment_runtime_records_params.UsageListEnvironmentRuntimeRecordsParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    usage_list_environment_runtime_records_params.UsageListEnvironmentRuntimeRecordsParams,
+                ),
+            ),
+            model=EnvironmentUsageRecord,
+            method="post",
+        )
+
+
+class AsyncUsageResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncUsageResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncUsageResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncUsageResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncUsageResourceWithStreamingResponse(self)
+
+    def list_environment_runtime_records(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: usage_list_environment_runtime_records_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: usage_list_environment_runtime_records_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[EnvironmentUsageRecord, AsyncRecordsPage[EnvironmentUsageRecord]]:
+        """
+        Lists completed environment runtime records within a specified date range.
+
+        Returns a list of environment runtime records that were completed within the
+        specified date range. Records of currently running environments are not
+        included.
+
+        Use this method to:
+
+        - View environment runtime records
+        - Filter by project
+        - Create custom usage reports
+
+        ### Example
+
+        ```yaml
+        filter:
+          projectId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          dateRange:
+            startTime: "2024-01-01T00:00:00Z"
+            endTime: "2024-01-02T00:00:00Z"
+        pagination:
+          pageSize: 100
+        ```
+
+        Args:
+          filter: Filter options.
+
+          pagination: Pagination options.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.UsageService/ListEnvironmentUsageRecords",
+            page=AsyncRecordsPage[EnvironmentUsageRecord],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                usage_list_environment_runtime_records_params.UsageListEnvironmentRuntimeRecordsParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    usage_list_environment_runtime_records_params.UsageListEnvironmentRuntimeRecordsParams,
+                ),
+            ),
+            model=EnvironmentUsageRecord,
+            method="post",
+        )
+
+
+class UsageResourceWithRawResponse:
+    def __init__(self, usage: UsageResource) -> None:
+        self._usage = usage
+
+        self.list_environment_runtime_records = to_raw_response_wrapper(
+            usage.list_environment_runtime_records,
+        )
+
+
+class AsyncUsageResourceWithRawResponse:
+    def __init__(self, usage: AsyncUsageResource) -> None:
+        self._usage = usage
+
+        self.list_environment_runtime_records = async_to_raw_response_wrapper(
+            usage.list_environment_runtime_records,
+        )
+
+
+class UsageResourceWithStreamingResponse:
+    def __init__(self, usage: UsageResource) -> None:
+        self._usage = usage
+
+        self.list_environment_runtime_records = to_streamed_response_wrapper(
+            usage.list_environment_runtime_records,
+        )
+
+
+class AsyncUsageResourceWithStreamingResponse:
+    def __init__(self, usage: AsyncUsageResource) -> None:
+        self._usage = usage
+
+        self.list_environment_runtime_records = async_to_streamed_response_wrapper(
+            usage.list_environment_runtime_records,
+        )
diff --git a/src/gitpod/resources/users/__init__.py b/src/gitpod/resources/users/__init__.py
new file mode 100644
index 0000000..7250286
--- /dev/null
+++ b/src/gitpod/resources/users/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .pats import (
+    PatsResource,
+    AsyncPatsResource,
+    PatsResourceWithRawResponse,
+    AsyncPatsResourceWithRawResponse,
+    PatsResourceWithStreamingResponse,
+    AsyncPatsResourceWithStreamingResponse,
+)
+from .users import (
+    UsersResource,
+    AsyncUsersResource,
+    UsersResourceWithRawResponse,
+    AsyncUsersResourceWithRawResponse,
+    UsersResourceWithStreamingResponse,
+    AsyncUsersResourceWithStreamingResponse,
+)
+from .dotfiles import (
+    DotfilesResource,
+    AsyncDotfilesResource,
+    DotfilesResourceWithRawResponse,
+    AsyncDotfilesResourceWithRawResponse,
+    DotfilesResourceWithStreamingResponse,
+    AsyncDotfilesResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "DotfilesResource",
+    "AsyncDotfilesResource",
+    "DotfilesResourceWithRawResponse",
+    "AsyncDotfilesResourceWithRawResponse",
+    "DotfilesResourceWithStreamingResponse",
+    "AsyncDotfilesResourceWithStreamingResponse",
+    "PatsResource",
+    "AsyncPatsResource",
+    "PatsResourceWithRawResponse",
+    "AsyncPatsResourceWithRawResponse",
+    "PatsResourceWithStreamingResponse",
+    "AsyncPatsResourceWithStreamingResponse",
+    "UsersResource",
+    "AsyncUsersResource",
+    "UsersResourceWithRawResponse",
+    "AsyncUsersResourceWithRawResponse",
+    "UsersResourceWithStreamingResponse",
+    "AsyncUsersResourceWithStreamingResponse",
+]
diff --git a/src/gitpod/resources/users/dotfiles.py b/src/gitpod/resources/users/dotfiles.py
new file mode 100644
index 0000000..6e14987
--- /dev/null
+++ b/src/gitpod/resources/users/dotfiles.py
@@ -0,0 +1,313 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...types.users import dotfile_get_params, dotfile_set_params
+from ..._base_client import make_request_options
+from ...types.users.dotfile_get_response import DotfileGetResponse
+
+__all__ = ["DotfilesResource", "AsyncDotfilesResource"]
+
+
+class DotfilesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> DotfilesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return DotfilesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> DotfilesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return DotfilesResourceWithStreamingResponse(self)
+
+    def get(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DotfileGetResponse:
+        """
+        Gets the dotfiles for a user.
+
+        Use this method to:
+
+        - Retrieve user dotfiles
+
+        ### Examples
+
+        - Get dotfiles:
+
+          Retrieves the dotfiles for the current user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.UserService/GetDotfilesConfiguration",
+            body=maybe_transform({"empty": empty}, dotfile_get_params.DotfileGetParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DotfileGetResponse,
+        )
+
+    def set(
+        self,
+        *,
+        repository: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Sets the dotfiles configuration for a user.
+
+        Use this method to:
+
+        - Configure user dotfiles
+        - Update dotfiles settings
+
+        ### Examples
+
+        - Set dotfiles configuration:
+
+          Sets the dotfiles configuration for the current user.
+
+          ```yaml
+          { "repository": "https://github.com/gitpod-io/dotfiles" }
+          ```
+
+        - Remove dotfiles:
+
+          Removes the dotfiles for the current user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.UserService/SetDotfilesConfiguration",
+            body=maybe_transform({"repository": repository}, dotfile_set_params.DotfileSetParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncDotfilesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncDotfilesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncDotfilesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncDotfilesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncDotfilesResourceWithStreamingResponse(self)
+
+    async def get(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> DotfileGetResponse:
+        """
+        Gets the dotfiles for a user.
+
+        Use this method to:
+
+        - Retrieve user dotfiles
+
+        ### Examples
+
+        - Get dotfiles:
+
+          Retrieves the dotfiles for the current user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.UserService/GetDotfilesConfiguration",
+            body=await async_maybe_transform({"empty": empty}, dotfile_get_params.DotfileGetParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=DotfileGetResponse,
+        )
+
+    async def set(
+        self,
+        *,
+        repository: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Sets the dotfiles configuration for a user.
+
+        Use this method to:
+
+        - Configure user dotfiles
+        - Update dotfiles settings
+
+        ### Examples
+
+        - Set dotfiles configuration:
+
+          Sets the dotfiles configuration for the current user.
+
+          ```yaml
+          { "repository": "https://github.com/gitpod-io/dotfiles" }
+          ```
+
+        - Remove dotfiles:
+
+          Removes the dotfiles for the current user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.UserService/SetDotfilesConfiguration",
+            body=await async_maybe_transform({"repository": repository}, dotfile_set_params.DotfileSetParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class DotfilesResourceWithRawResponse:
+    def __init__(self, dotfiles: DotfilesResource) -> None:
+        self._dotfiles = dotfiles
+
+        self.get = to_raw_response_wrapper(
+            dotfiles.get,
+        )
+        self.set = to_raw_response_wrapper(
+            dotfiles.set,
+        )
+
+
+class AsyncDotfilesResourceWithRawResponse:
+    def __init__(self, dotfiles: AsyncDotfilesResource) -> None:
+        self._dotfiles = dotfiles
+
+        self.get = async_to_raw_response_wrapper(
+            dotfiles.get,
+        )
+        self.set = async_to_raw_response_wrapper(
+            dotfiles.set,
+        )
+
+
+class DotfilesResourceWithStreamingResponse:
+    def __init__(self, dotfiles: DotfilesResource) -> None:
+        self._dotfiles = dotfiles
+
+        self.get = to_streamed_response_wrapper(
+            dotfiles.get,
+        )
+        self.set = to_streamed_response_wrapper(
+            dotfiles.set,
+        )
+
+
+class AsyncDotfilesResourceWithStreamingResponse:
+    def __init__(self, dotfiles: AsyncDotfilesResource) -> None:
+        self._dotfiles = dotfiles
+
+        self.get = async_to_streamed_response_wrapper(
+            dotfiles.get,
+        )
+        self.set = async_to_streamed_response_wrapper(
+            dotfiles.set,
+        )
diff --git a/src/gitpod/resources/users/pats.py b/src/gitpod/resources/users/pats.py
new file mode 100644
index 0000000..95511b8
--- /dev/null
+++ b/src/gitpod/resources/users/pats.py
@@ -0,0 +1,467 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncPersonalAccessTokensPage, AsyncPersonalAccessTokensPage
+from ...types.users import pat_get_params, pat_list_params, pat_delete_params
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.users.pat_get_response import PatGetResponse
+from ...types.users.personal_access_token import PersonalAccessToken
+
+__all__ = ["PatsResource", "AsyncPatsResource"]
+
+
+class PatsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> PatsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return PatsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> PatsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return PatsResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: pat_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: pat_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncPersonalAccessTokensPage[PersonalAccessToken]:
+        """
+        Lists personal access tokens with optional filtering.
+
+        Use this method to:
+
+        - View all active tokens
+        - Audit token usage
+        - Manage token lifecycle
+
+        ### Examples
+
+        - List user tokens:
+
+          Shows all tokens for specific users.
+
+          ```yaml
+          filter:
+            userIds: ["f53d2330-3795-4c5d-a1f3-453121af9c60"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.UserService/ListPersonalAccessTokens",
+            page=SyncPersonalAccessTokensPage[PersonalAccessToken],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                pat_list_params.PatListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    pat_list_params.PatListParams,
+                ),
+            ),
+            model=PersonalAccessToken,
+            method="post",
+        )
+
+    def delete(
+        self,
+        *,
+        personal_access_token_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a personal access token.
+
+        Use this method to:
+
+        - Revoke token access
+        - Remove unused tokens
+        - Rotate credentials
+
+        ### Examples
+
+        - Delete token:
+
+          Permanently revokes a token.
+
+          ```yaml
+          personalAccessTokenId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.UserService/DeletePersonalAccessToken",
+            body=maybe_transform(
+                {"personal_access_token_id": personal_access_token_id}, pat_delete_params.PatDeleteParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    def get(
+        self,
+        *,
+        personal_access_token_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PatGetResponse:
+        """
+        Gets details about a specific personal access token.
+
+        Use this method to:
+
+        - View token metadata
+        - Check token expiration
+        - Monitor token usage
+
+        ### Examples
+
+        - Get token details:
+
+          Retrieves information about a specific token.
+
+          ```yaml
+          personalAccessTokenId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.UserService/GetPersonalAccessToken",
+            body=maybe_transform({"personal_access_token_id": personal_access_token_id}, pat_get_params.PatGetParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PatGetResponse,
+        )
+
+
+class AsyncPatsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncPatsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncPatsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncPatsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncPatsResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        token: str | NotGiven = NOT_GIVEN,
+        page_size: int | NotGiven = NOT_GIVEN,
+        filter: pat_list_params.Filter | NotGiven = NOT_GIVEN,
+        pagination: pat_list_params.Pagination | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[PersonalAccessToken, AsyncPersonalAccessTokensPage[PersonalAccessToken]]:
+        """
+        Lists personal access tokens with optional filtering.
+
+        Use this method to:
+
+        - View all active tokens
+        - Audit token usage
+        - Manage token lifecycle
+
+        ### Examples
+
+        - List user tokens:
+
+          Shows all tokens for specific users.
+
+          ```yaml
+          filter:
+            userIds: ["f53d2330-3795-4c5d-a1f3-453121af9c60"]
+          pagination:
+            pageSize: 20
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get_api_list(
+            "/gitpod.v1.UserService/ListPersonalAccessTokens",
+            page=AsyncPersonalAccessTokensPage[PersonalAccessToken],
+            body=maybe_transform(
+                {
+                    "filter": filter,
+                    "pagination": pagination,
+                },
+                pat_list_params.PatListParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "token": token,
+                        "page_size": page_size,
+                    },
+                    pat_list_params.PatListParams,
+                ),
+            ),
+            model=PersonalAccessToken,
+            method="post",
+        )
+
+    async def delete(
+        self,
+        *,
+        personal_access_token_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Deletes a personal access token.
+
+        Use this method to:
+
+        - Revoke token access
+        - Remove unused tokens
+        - Rotate credentials
+
+        ### Examples
+
+        - Delete token:
+
+          Permanently revokes a token.
+
+          ```yaml
+          personalAccessTokenId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.UserService/DeletePersonalAccessToken",
+            body=await async_maybe_transform(
+                {"personal_access_token_id": personal_access_token_id}, pat_delete_params.PatDeleteParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+    async def get(
+        self,
+        *,
+        personal_access_token_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> PatGetResponse:
+        """
+        Gets details about a specific personal access token.
+
+        Use this method to:
+
+        - View token metadata
+        - Check token expiration
+        - Monitor token usage
+
+        ### Examples
+
+        - Get token details:
+
+          Retrieves information about a specific token.
+
+          ```yaml
+          personalAccessTokenId: "d2c94c27-3b76-4a42-b88c-95a85e392c68"
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.UserService/GetPersonalAccessToken",
+            body=await async_maybe_transform(
+                {"personal_access_token_id": personal_access_token_id}, pat_get_params.PatGetParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=PatGetResponse,
+        )
+
+
+class PatsResourceWithRawResponse:
+    def __init__(self, pats: PatsResource) -> None:
+        self._pats = pats
+
+        self.list = to_raw_response_wrapper(
+            pats.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            pats.delete,
+        )
+        self.get = to_raw_response_wrapper(
+            pats.get,
+        )
+
+
+class AsyncPatsResourceWithRawResponse:
+    def __init__(self, pats: AsyncPatsResource) -> None:
+        self._pats = pats
+
+        self.list = async_to_raw_response_wrapper(
+            pats.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            pats.delete,
+        )
+        self.get = async_to_raw_response_wrapper(
+            pats.get,
+        )
+
+
+class PatsResourceWithStreamingResponse:
+    def __init__(self, pats: PatsResource) -> None:
+        self._pats = pats
+
+        self.list = to_streamed_response_wrapper(
+            pats.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            pats.delete,
+        )
+        self.get = to_streamed_response_wrapper(
+            pats.get,
+        )
+
+
+class AsyncPatsResourceWithStreamingResponse:
+    def __init__(self, pats: AsyncPatsResource) -> None:
+        self._pats = pats
+
+        self.list = async_to_streamed_response_wrapper(
+            pats.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            pats.delete,
+        )
+        self.get = async_to_streamed_response_wrapper(
+            pats.get,
+        )
diff --git a/src/gitpod/resources/users/users.py b/src/gitpod/resources/users/users.py
new file mode 100644
index 0000000..0ffe276
--- /dev/null
+++ b/src/gitpod/resources/users/users.py
@@ -0,0 +1,405 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .pats import (
+    PatsResource,
+    AsyncPatsResource,
+    PatsResourceWithRawResponse,
+    AsyncPatsResourceWithRawResponse,
+    PatsResourceWithStreamingResponse,
+    AsyncPatsResourceWithStreamingResponse,
+)
+from ...types import user_set_suspended_params, user_get_authenticated_user_params
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from .dotfiles import (
+    DotfilesResource,
+    AsyncDotfilesResource,
+    DotfilesResourceWithRawResponse,
+    AsyncDotfilesResourceWithRawResponse,
+    DotfilesResourceWithStreamingResponse,
+    AsyncDotfilesResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.user_get_authenticated_user_response import UserGetAuthenticatedUserResponse
+
+__all__ = ["UsersResource", "AsyncUsersResource"]
+
+
+class UsersResource(SyncAPIResource):
+    @cached_property
+    def dotfiles(self) -> DotfilesResource:
+        return DotfilesResource(self._client)
+
+    @cached_property
+    def pats(self) -> PatsResource:
+        return PatsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> UsersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return UsersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> UsersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return UsersResourceWithStreamingResponse(self)
+
+    def get_authenticated_user(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserGetAuthenticatedUserResponse:
+        """
+        Gets information about the currently authenticated user.
+
+        Use this method to:
+
+        - Get user profile information
+        - Check authentication status
+        - Retrieve user settings
+        - Verify account details
+
+        ### Examples
+
+        - Get current user:
+
+          Retrieves details about the authenticated user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.UserService/GetAuthenticatedUser",
+            body=maybe_transform({"empty": empty}, user_get_authenticated_user_params.UserGetAuthenticatedUserParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserGetAuthenticatedUserResponse,
+        )
+
+    def set_suspended(
+        self,
+        *,
+        suspended: bool | NotGiven = NOT_GIVEN,
+        user_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Sets whether a user account is suspended.
+
+        Use this method to:
+
+        - Suspend problematic users
+        - Reactivate suspended accounts
+        - Manage user access
+
+        ### Examples
+
+        - Suspend user:
+
+          Suspends a user account.
+
+          ```yaml
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          suspended: true
+          ```
+
+        - Reactivate user:
+
+          Removes suspension from a user account.
+
+          ```yaml
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          suspended: false
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/gitpod.v1.UserService/SetSuspended",
+            body=maybe_transform(
+                {
+                    "suspended": suspended,
+                    "user_id": user_id,
+                },
+                user_set_suspended_params.UserSetSuspendedParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncUsersResource(AsyncAPIResource):
+    @cached_property
+    def dotfiles(self) -> AsyncDotfilesResource:
+        return AsyncDotfilesResource(self._client)
+
+    @cached_property
+    def pats(self) -> AsyncPatsResource:
+        return AsyncPatsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncUsersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncUsersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncUsersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/gitpod-io/gitpod-sdk-python#with_streaming_response
+        """
+        return AsyncUsersResourceWithStreamingResponse(self)
+
+    async def get_authenticated_user(
+        self,
+        *,
+        empty: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserGetAuthenticatedUserResponse:
+        """
+        Gets information about the currently authenticated user.
+
+        Use this method to:
+
+        - Get user profile information
+        - Check authentication status
+        - Retrieve user settings
+        - Verify account details
+
+        ### Examples
+
+        - Get current user:
+
+          Retrieves details about the authenticated user.
+
+          ```yaml
+          {}
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.UserService/GetAuthenticatedUser",
+            body=await async_maybe_transform(
+                {"empty": empty}, user_get_authenticated_user_params.UserGetAuthenticatedUserParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserGetAuthenticatedUserResponse,
+        )
+
+    async def set_suspended(
+        self,
+        *,
+        suspended: bool | NotGiven = NOT_GIVEN,
+        user_id: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Sets whether a user account is suspended.
+
+        Use this method to:
+
+        - Suspend problematic users
+        - Reactivate suspended accounts
+        - Manage user access
+
+        ### Examples
+
+        - Suspend user:
+
+          Suspends a user account.
+
+          ```yaml
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          suspended: true
+          ```
+
+        - Reactivate user:
+
+          Removes suspension from a user account.
+
+          ```yaml
+          userId: "f53d2330-3795-4c5d-a1f3-453121af9c60"
+          suspended: false
+          ```
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/gitpod.v1.UserService/SetSuspended",
+            body=await async_maybe_transform(
+                {
+                    "suspended": suspended,
+                    "user_id": user_id,
+                },
+                user_set_suspended_params.UserSetSuspendedParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class UsersResourceWithRawResponse:
+    def __init__(self, users: UsersResource) -> None:
+        self._users = users
+
+        self.get_authenticated_user = to_raw_response_wrapper(
+            users.get_authenticated_user,
+        )
+        self.set_suspended = to_raw_response_wrapper(
+            users.set_suspended,
+        )
+
+    @cached_property
+    def dotfiles(self) -> DotfilesResourceWithRawResponse:
+        return DotfilesResourceWithRawResponse(self._users.dotfiles)
+
+    @cached_property
+    def pats(self) -> PatsResourceWithRawResponse:
+        return PatsResourceWithRawResponse(self._users.pats)
+
+
+class AsyncUsersResourceWithRawResponse:
+    def __init__(self, users: AsyncUsersResource) -> None:
+        self._users = users
+
+        self.get_authenticated_user = async_to_raw_response_wrapper(
+            users.get_authenticated_user,
+        )
+        self.set_suspended = async_to_raw_response_wrapper(
+            users.set_suspended,
+        )
+
+    @cached_property
+    def dotfiles(self) -> AsyncDotfilesResourceWithRawResponse:
+        return AsyncDotfilesResourceWithRawResponse(self._users.dotfiles)
+
+    @cached_property
+    def pats(self) -> AsyncPatsResourceWithRawResponse:
+        return AsyncPatsResourceWithRawResponse(self._users.pats)
+
+
+class UsersResourceWithStreamingResponse:
+    def __init__(self, users: UsersResource) -> None:
+        self._users = users
+
+        self.get_authenticated_user = to_streamed_response_wrapper(
+            users.get_authenticated_user,
+        )
+        self.set_suspended = to_streamed_response_wrapper(
+            users.set_suspended,
+        )
+
+    @cached_property
+    def dotfiles(self) -> DotfilesResourceWithStreamingResponse:
+        return DotfilesResourceWithStreamingResponse(self._users.dotfiles)
+
+    @cached_property
+    def pats(self) -> PatsResourceWithStreamingResponse:
+        return PatsResourceWithStreamingResponse(self._users.pats)
+
+
+class AsyncUsersResourceWithStreamingResponse:
+    def __init__(self, users: AsyncUsersResource) -> None:
+        self._users = users
+
+        self.get_authenticated_user = async_to_streamed_response_wrapper(
+            users.get_authenticated_user,
+        )
+        self.set_suspended = async_to_streamed_response_wrapper(
+            users.set_suspended,
+        )
+
+    @cached_property
+    def dotfiles(self) -> AsyncDotfilesResourceWithStreamingResponse:
+        return AsyncDotfilesResourceWithStreamingResponse(self._users.dotfiles)
+
+    @cached_property
+    def pats(self) -> AsyncPatsResourceWithStreamingResponse:
+        return AsyncPatsResourceWithStreamingResponse(self._users.pats)
diff --git a/src/gitpod/types/__init__.py b/src/gitpod/types/__init__.py
new file mode 100644
index 0000000..cfa1c5d
--- /dev/null
+++ b/src/gitpod/types/__init__.py
@@ -0,0 +1,189 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .user import User as User
+from .group import Group as Group
+from .editor import Editor as Editor
+from .runner import Runner as Runner
+from .secret import Secret as Secret
+from .shared import (
+    Task as Task,
+    RunsOn as RunsOn,
+    Gateway as Gateway,
+    Subject as Subject,
+    TaskSpec as TaskSpec,
+    ErrorCode as ErrorCode,
+    Principal as Principal,
+    FieldValue as FieldValue,
+    UserStatus as UserStatus,
+    TaskMetadata as TaskMetadata,
+    TaskExecution as TaskExecution,
+    EnvironmentClass as EnvironmentClass,
+    OrganizationRole as OrganizationRole,
+    AutomationTrigger as AutomationTrigger,
+    TaskExecutionSpec as TaskExecutionSpec,
+    TaskExecutionPhase as TaskExecutionPhase,
+    TaskExecutionStatus as TaskExecutionStatus,
+    TaskExecutionMetadata as TaskExecutionMetadata,
+)
+from .account import Account as Account
+from .project import Project as Project
+from .log_level import LogLevel as LogLevel
+from .environment import Environment as Environment
+from .runner_kind import RunnerKind as RunnerKind
+from .runner_spec import RunnerSpec as RunnerSpec
+from .gateway_info import GatewayInfo as GatewayInfo
+from .organization import Organization as Organization
+from .runner_phase import RunnerPhase as RunnerPhase
+from .secret_scope import SecretScope as SecretScope
+from .resource_type import ResourceType as ResourceType
+from .runner_status import RunnerStatus as RunnerStatus
+from .invite_domains import InviteDomains as InviteDomains
+from .login_provider import LoginProvider as LoginProvider
+from .admission_level import AdmissionLevel as AdmissionLevel
+from .runner_provider import RunnerProvider as RunnerProvider
+from .environment_spec import EnvironmentSpec as EnvironmentSpec
+from .id_token_version import IDTokenVersion as IDTokenVersion
+from .project_metadata import ProjectMetadata as ProjectMetadata
+from .environment_phase import EnvironmentPhase as EnvironmentPhase
+from .event_list_params import EventListParams as EventListParams
+from .group_list_params import GroupListParams as GroupListParams
+from .organization_tier import OrganizationTier as OrganizationTier
+from .runner_capability import RunnerCapability as RunnerCapability
+from .runner_spec_param import RunnerSpecParam as RunnerSpecParam
+from .account_membership import AccountMembership as AccountMembership
+from .editor_list_params import EditorListParams as EditorListParams
+from .environment_status import EnvironmentStatus as EnvironmentStatus
+from .event_watch_params import EventWatchParams as EventWatchParams
+from .resource_operation import ResourceOperation as ResourceOperation
+from .runner_list_params import RunnerListParams as RunnerListParams
+from .secret_list_params import SecretListParams as SecretListParams
+from .secret_scope_param import SecretScopeParam as SecretScopeParam
+from .event_list_response import EventListResponse as EventListResponse
+from .gateway_list_params import GatewayListParams as GatewayListParams
+from .organization_member import OrganizationMember as OrganizationMember
+from .project_list_params import ProjectListParams as ProjectListParams
+from .environment_metadata import EnvironmentMetadata as EnvironmentMetadata
+from .event_watch_response import EventWatchResponse as EventWatchResponse
+from .invite_domains_param import InviteDomainsParam as InviteDomainsParam
+from .runner_configuration import RunnerConfiguration as RunnerConfiguration
+from .runner_create_params import RunnerCreateParams as RunnerCreateParams
+from .runner_delete_params import RunnerDeleteParams as RunnerDeleteParams
+from .runner_update_params import RunnerUpdateParams as RunnerUpdateParams
+from .secret_create_params import SecretCreateParams as SecretCreateParams
+from .secret_delete_params import SecretDeleteParams as SecretDeleteParams
+from .account_delete_params import AccountDeleteParams as AccountDeleteParams
+from .joinable_organization import JoinableOrganization as JoinableOrganization
+from .metrics_configuration import MetricsConfiguration as MetricsConfiguration
+from .project_create_params import ProjectCreateParams as ProjectCreateParams
+from .project_delete_params import ProjectDeleteParams as ProjectDeleteParams
+from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
+from .editor_retrieve_params import EditorRetrieveParams as EditorRetrieveParams
+from .environment_spec_param import EnvironmentSpecParam as EnvironmentSpecParam
+from .runner_create_response import RunnerCreateResponse as RunnerCreateResponse
+from .runner_release_channel import RunnerReleaseChannel as RunnerReleaseChannel
+from .runner_retrieve_params import RunnerRetrieveParams as RunnerRetrieveParams
+from .secret_create_response import SecretCreateResponse as SecretCreateResponse
+from .account_retrieve_params import AccountRetrieveParams as AccountRetrieveParams
+from .environment_initializer import EnvironmentInitializer as EnvironmentInitializer
+from .environment_list_params import EnvironmentListParams as EnvironmentListParams
+from .environment_stop_params import EnvironmentStopParams as EnvironmentStopParams
+from .project_create_response import ProjectCreateResponse as ProjectCreateResponse
+from .project_retrieve_params import ProjectRetrieveParams as ProjectRetrieveParams
+from .project_update_response import ProjectUpdateResponse as ProjectUpdateResponse
+from .secret_get_value_params import SecretGetValueParams as SecretGetValueParams
+from .editor_retrieve_response import EditorRetrieveResponse as EditorRetrieveResponse
+from .environment_start_params import EnvironmentStartParams as EnvironmentStartParams
+from .environment_usage_record import EnvironmentUsageRecord as EnvironmentUsageRecord
+from .organization_join_params import OrganizationJoinParams as OrganizationJoinParams
+from .runner_retrieve_response import RunnerRetrieveResponse as RunnerRetrieveResponse
+from .account_retrieve_response import AccountRetrieveResponse as AccountRetrieveResponse
+from .editor_resolve_url_params import EditorResolveURLParams as EditorResolveURLParams
+from .environment_create_params import EnvironmentCreateParams as EnvironmentCreateParams
+from .environment_delete_params import EnvironmentDeleteParams as EnvironmentDeleteParams
+from .environment_update_params import EnvironmentUpdateParams as EnvironmentUpdateParams
+from .organization_leave_params import OrganizationLeaveParams as OrganizationLeaveParams
+from .project_environment_class import ProjectEnvironmentClass as ProjectEnvironmentClass
+from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
+from .secret_get_value_response import SecretGetValueResponse as SecretGetValueResponse
+from .user_set_suspended_params import UserSetSuspendedParams as UserSetSuspendedParams
+from .organization_create_params import OrganizationCreateParams as OrganizationCreateParams
+from .organization_delete_params import OrganizationDeleteParams as OrganizationDeleteParams
+from .organization_join_response import OrganizationJoinResponse as OrganizationJoinResponse
+from .organization_update_params import OrganizationUpdateParams as OrganizationUpdateParams
+from .runner_configuration_param import RunnerConfigurationParam as RunnerConfigurationParam
+from .secret_update_value_params import SecretUpdateValueParams as SecretUpdateValueParams
+from .editor_resolve_url_response import EditorResolveURLResponse as EditorResolveURLResponse
+from .environment_activity_signal import EnvironmentActivitySignal as EnvironmentActivitySignal
+from .environment_create_response import EnvironmentCreateResponse as EnvironmentCreateResponse
+from .environment_retrieve_params import EnvironmentRetrieveParams as EnvironmentRetrieveParams
+from .metrics_configuration_param import MetricsConfigurationParam as MetricsConfigurationParam
+from .environment_unarchive_params import EnvironmentUnarchiveParams as EnvironmentUnarchiveParams
+from .identity_get_id_token_params import IdentityGetIDTokenParams as IdentityGetIDTokenParams
+from .organization_create_response import OrganizationCreateResponse as OrganizationCreateResponse
+from .organization_retrieve_params import OrganizationRetrieveParams as OrganizationRetrieveParams
+from .organization_set_role_params import OrganizationSetRoleParams as OrganizationSetRoleParams
+from .organization_update_response import OrganizationUpdateResponse as OrganizationUpdateResponse
+from .environment_initializer_param import EnvironmentInitializerParam as EnvironmentInitializerParam
+from .environment_retrieve_response import EnvironmentRetrieveResponse as EnvironmentRetrieveResponse
+from .environment_mark_active_params import EnvironmentMarkActiveParams as EnvironmentMarkActiveParams
+from .identity_exchange_token_params import IdentityExchangeTokenParams as IdentityExchangeTokenParams
+from .identity_get_id_token_response import IdentityGetIDTokenResponse as IdentityGetIDTokenResponse
+from .organization_retrieve_response import OrganizationRetrieveResponse as OrganizationRetrieveResponse
+from .project_environment_class_param import ProjectEnvironmentClassParam as ProjectEnvironmentClassParam
+from .runner_parse_context_url_params import RunnerParseContextURLParams as RunnerParseContextURLParams
+from .account_get_sso_login_url_params import AccountGetSSOLoginURLParams as AccountGetSSOLoginURLParams
+from .identity_exchange_token_response import IdentityExchangeTokenResponse as IdentityExchangeTokenResponse
+from .organization_list_members_params import OrganizationListMembersParams as OrganizationListMembersParams
+from .environment_activity_signal_param import EnvironmentActivitySignalParam as EnvironmentActivitySignalParam
+from .runner_create_runner_token_params import RunnerCreateRunnerTokenParams as RunnerCreateRunnerTokenParams
+from .runner_parse_context_url_response import RunnerParseContextURLResponse as RunnerParseContextURLResponse
+from .account_get_sso_login_url_response import AccountGetSSOLoginURLResponse as AccountGetSSOLoginURLResponse
+from .user_get_authenticated_user_params import UserGetAuthenticatedUserParams as UserGetAuthenticatedUserParams
+from .account_list_login_providers_params import AccountListLoginProvidersParams as AccountListLoginProvidersParams
+from .runner_create_runner_token_response import RunnerCreateRunnerTokenResponse as RunnerCreateRunnerTokenResponse
+from .environment_create_logs_token_params import EnvironmentCreateLogsTokenParams as EnvironmentCreateLogsTokenParams
+from .user_get_authenticated_user_response import UserGetAuthenticatedUserResponse as UserGetAuthenticatedUserResponse
+from .environment_create_from_project_params import (
+    EnvironmentCreateFromProjectParams as EnvironmentCreateFromProjectParams,
+)
+from .environment_create_logs_token_response import (
+    EnvironmentCreateLogsTokenResponse as EnvironmentCreateLogsTokenResponse,
+)
+from .project_create_from_environment_params import (
+    ProjectCreateFromEnvironmentParams as ProjectCreateFromEnvironmentParams,
+)
+from .environment_create_from_project_response import (
+    EnvironmentCreateFromProjectResponse as EnvironmentCreateFromProjectResponse,
+)
+from .project_create_from_environment_response import (
+    ProjectCreateFromEnvironmentResponse as ProjectCreateFromEnvironmentResponse,
+)
+from .account_list_joinable_organizations_params import (
+    AccountListJoinableOrganizationsParams as AccountListJoinableOrganizationsParams,
+)
+from .identity_get_authenticated_identity_params import (
+    IdentityGetAuthenticatedIdentityParams as IdentityGetAuthenticatedIdentityParams,
+)
+from .environment_create_environment_token_params import (
+    EnvironmentCreateEnvironmentTokenParams as EnvironmentCreateEnvironmentTokenParams,
+)
+from .runner_check_authentication_for_host_params import (
+    RunnerCheckAuthenticationForHostParams as RunnerCheckAuthenticationForHostParams,
+)
+from .account_list_joinable_organizations_response import (
+    AccountListJoinableOrganizationsResponse as AccountListJoinableOrganizationsResponse,
+)
+from .identity_get_authenticated_identity_response import (
+    IdentityGetAuthenticatedIdentityResponse as IdentityGetAuthenticatedIdentityResponse,
+)
+from .environment_create_environment_token_response import (
+    EnvironmentCreateEnvironmentTokenResponse as EnvironmentCreateEnvironmentTokenResponse,
+)
+from .runner_check_authentication_for_host_response import (
+    RunnerCheckAuthenticationForHostResponse as RunnerCheckAuthenticationForHostResponse,
+)
+from .usage_list_environment_runtime_records_params import (
+    UsageListEnvironmentRuntimeRecordsParams as UsageListEnvironmentRuntimeRecordsParams,
+)
diff --git a/src/gitpod/types/account.py b/src/gitpod/types/account.py
new file mode 100644
index 0000000..41ee046
--- /dev/null
+++ b/src/gitpod/types/account.py
@@ -0,0 +1,223 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .account_membership import AccountMembership
+from .joinable_organization import JoinableOrganization
+
+__all__ = ["Account"]
+
+
+class Account(BaseModel):
+    id: str
+
+    created_at: datetime = FieldInfo(alias="createdAt")
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    email: str
+
+    name: str
+
+    updated_at: datetime = FieldInfo(alias="updatedAt")
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    avatar_url: Optional[str] = FieldInfo(alias="avatarUrl", default=None)
+
+    joinables: Optional[List[JoinableOrganization]] = None
+    """joinables is deprecated. Use ListJoinableOrganizations instead."""
+
+    memberships: Optional[List[AccountMembership]] = None
+
+    organization_id: Optional[str] = FieldInfo(alias="organizationId", default=None)
+    """
+    organization_id is the ID of the organization the account is owned by if it's
+    created through custom SSO
+    """
+
+    public_email_provider: Optional[bool] = FieldInfo(alias="publicEmailProvider", default=None)
+    """
+    public_email_provider is true if the email for the Account matches a known
+    public email provider
+    """
diff --git a/src/gitpod/types/account_delete_params.py b/src/gitpod/types/account_delete_params.py
new file mode 100644
index 0000000..a049132
--- /dev/null
+++ b/src/gitpod/types/account_delete_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["AccountDeleteParams"]
+
+
+class AccountDeleteParams(TypedDict, total=False):
+    account_id: Required[Annotated[str, PropertyInfo(alias="accountId")]]
diff --git a/src/gitpod/types/account_get_sso_login_url_params.py b/src/gitpod/types/account_get_sso_login_url_params.py
new file mode 100644
index 0000000..4551e59
--- /dev/null
+++ b/src/gitpod/types/account_get_sso_login_url_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["AccountGetSSOLoginURLParams"]
+
+
+class AccountGetSSOLoginURLParams(TypedDict, total=False):
+    email: Required[str]
+    """email is the email the user wants to login with"""
+
+    return_to: Annotated[Optional[str], PropertyInfo(alias="returnTo")]
+    """return_to is the URL the user will be redirected to after login"""
diff --git a/src/gitpod/types/account_get_sso_login_url_response.py b/src/gitpod/types/account_get_sso_login_url_response.py
new file mode 100644
index 0000000..fd289cd
--- /dev/null
+++ b/src/gitpod/types/account_get_sso_login_url_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["AccountGetSSOLoginURLResponse"]
+
+
+class AccountGetSSOLoginURLResponse(BaseModel):
+    login_url: str = FieldInfo(alias="loginUrl")
+    """login_url is the URL to redirect the user to for SSO login"""
diff --git a/src/gitpod/types/account_list_joinable_organizations_params.py b/src/gitpod/types/account_list_joinable_organizations_params.py
new file mode 100644
index 0000000..d4531fb
--- /dev/null
+++ b/src/gitpod/types/account_list_joinable_organizations_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["AccountListJoinableOrganizationsParams"]
+
+
+class AccountListJoinableOrganizationsParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    empty: bool
diff --git a/src/gitpod/types/account_list_joinable_organizations_response.py b/src/gitpod/types/account_list_joinable_organizations_response.py
new file mode 100644
index 0000000..3253ba5
--- /dev/null
+++ b/src/gitpod/types/account_list_joinable_organizations_response.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .joinable_organization import JoinableOrganization
+
+__all__ = ["AccountListJoinableOrganizationsResponse"]
+
+
+class AccountListJoinableOrganizationsResponse(BaseModel):
+    joinable_organizations: Optional[List[JoinableOrganization]] = FieldInfo(
+        alias="joinableOrganizations", default=None
+    )
diff --git a/src/gitpod/types/account_list_login_providers_params.py b/src/gitpod/types/account_list_login_providers_params.py
new file mode 100644
index 0000000..e7c5da7
--- /dev/null
+++ b/src/gitpod/types/account_list_login_providers_params.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["AccountListLoginProvidersParams", "Filter", "Pagination"]
+
+
+class AccountListLoginProvidersParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+    """filter contains the filter options for listing login methods"""
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing login methods"""
+
+
+class Filter(TypedDict, total=False):
+    invite_id: Annotated[str, PropertyInfo(alias="inviteId")]
+    """invite_id is the ID of the invite URL the user wants to login with"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/account_membership.py b/src/gitpod/types/account_membership.py
new file mode 100644
index 0000000..10f2603
--- /dev/null
+++ b/src/gitpod/types/account_membership.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.organization_role import OrganizationRole
+
+__all__ = ["AccountMembership"]
+
+
+class AccountMembership(BaseModel):
+    organization_id: str = FieldInfo(alias="organizationId")
+    """organization_id is the id of the organization the user is a member of"""
+
+    organization_name: str = FieldInfo(alias="organizationName")
+    """organization_name is the name of the organization the user is a member of"""
+
+    user_id: str = FieldInfo(alias="userId")
+    """user_id is the ID the user has in the organization"""
+
+    user_role: OrganizationRole = FieldInfo(alias="userRole")
+    """user_role is the role the user has in the organization"""
+
+    organization_member_count: Optional[int] = FieldInfo(alias="organizationMemberCount", default=None)
+    """
+    organization_name is the member count of the organization the user is a member
+    of
+    """
diff --git a/src/gitpod/types/account_retrieve_params.py b/src/gitpod/types/account_retrieve_params.py
new file mode 100644
index 0000000..68642eb
--- /dev/null
+++ b/src/gitpod/types/account_retrieve_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["AccountRetrieveParams"]
+
+
+class AccountRetrieveParams(TypedDict, total=False):
+    empty: bool
diff --git a/src/gitpod/types/account_retrieve_response.py b/src/gitpod/types/account_retrieve_response.py
new file mode 100644
index 0000000..c32afbb
--- /dev/null
+++ b/src/gitpod/types/account_retrieve_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .account import Account
+from .._models import BaseModel
+
+__all__ = ["AccountRetrieveResponse"]
+
+
+class AccountRetrieveResponse(BaseModel):
+    account: Account
diff --git a/src/gitpod/types/admission_level.py b/src/gitpod/types/admission_level.py
new file mode 100644
index 0000000..40ee48d
--- /dev/null
+++ b/src/gitpod/types/admission_level.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["AdmissionLevel"]
+
+AdmissionLevel: TypeAlias = Literal[
+    "ADMISSION_LEVEL_UNSPECIFIED", "ADMISSION_LEVEL_OWNER_ONLY", "ADMISSION_LEVEL_EVERYONE"
+]
diff --git a/src/gitpod/types/editor.py b/src/gitpod/types/editor.py
new file mode 100644
index 0000000..96eebfa
--- /dev/null
+++ b/src/gitpod/types/editor.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["Editor"]
+
+
+class Editor(BaseModel):
+    id: str
+
+    installation_instructions: str = FieldInfo(alias="installationInstructions")
+
+    name: str
+
+    url_template: str = FieldInfo(alias="urlTemplate")
+
+    alias: Optional[str] = None
+
+    icon_url: Optional[str] = FieldInfo(alias="iconUrl", default=None)
+
+    short_description: Optional[str] = FieldInfo(alias="shortDescription", default=None)
diff --git a/src/gitpod/types/editor_list_params.py b/src/gitpod/types/editor_list_params.py
new file mode 100644
index 0000000..42f9426
--- /dev/null
+++ b/src/gitpod/types/editor_list_params.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EditorListParams", "Filter", "Pagination"]
+
+
+class EditorListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+    """filter contains the filter options for listing editors"""
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing environments"""
+
+
+class Filter(TypedDict, total=False):
+    allowed_by_policy: Annotated[bool, PropertyInfo(alias="allowedByPolicy")]
+    """
+    allowed_by_policy filters the response to only editors that are allowed by the
+    policies enforced in the organization
+    """
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/editor_resolve_url_params.py b/src/gitpod/types/editor_resolve_url_params.py
new file mode 100644
index 0000000..de698ba
--- /dev/null
+++ b/src/gitpod/types/editor_resolve_url_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EditorResolveURLParams"]
+
+
+class EditorResolveURLParams(TypedDict, total=False):
+    editor_id: Required[Annotated[str, PropertyInfo(alias="editorId")]]
+    """editorId is the ID of the editor to resolve the URL for"""
+
+    environment_id: Required[Annotated[str, PropertyInfo(alias="environmentId")]]
+    """environmentId is the ID of the environment to resolve the URL for"""
+
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organizationId is the ID of the organization to resolve the URL for"""
diff --git a/src/gitpod/types/editor_resolve_url_response.py b/src/gitpod/types/editor_resolve_url_response.py
new file mode 100644
index 0000000..9633464
--- /dev/null
+++ b/src/gitpod/types/editor_resolve_url_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["EditorResolveURLResponse"]
+
+
+class EditorResolveURLResponse(BaseModel):
+    url: str
+    """url is the resolved editor URL"""
diff --git a/src/gitpod/types/editor_retrieve_params.py b/src/gitpod/types/editor_retrieve_params.py
new file mode 100644
index 0000000..f9f5194
--- /dev/null
+++ b/src/gitpod/types/editor_retrieve_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["EditorRetrieveParams"]
+
+
+class EditorRetrieveParams(TypedDict, total=False):
+    id: Required[str]
+    """id is the ID of the editor to get"""
diff --git a/src/gitpod/types/editor_retrieve_response.py b/src/gitpod/types/editor_retrieve_response.py
new file mode 100644
index 0000000..0a40810
--- /dev/null
+++ b/src/gitpod/types/editor_retrieve_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .editor import Editor
+from .._models import BaseModel
+
+__all__ = ["EditorRetrieveResponse"]
+
+
+class EditorRetrieveResponse(BaseModel):
+    editor: Editor
+    """editor contains the editor"""
diff --git a/src/gitpod/types/environment.py b/src/gitpod/types/environment.py
new file mode 100644
index 0000000..560cf25
--- /dev/null
+++ b/src/gitpod/types/environment.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .environment_spec import EnvironmentSpec
+from .environment_status import EnvironmentStatus
+from .environment_metadata import EnvironmentMetadata
+
+__all__ = ["Environment"]
+
+
+class Environment(BaseModel):
+    id: str
+    """ID is a unique identifier of this environment.
+
+    No other environment with the same name must be managed by this environment
+    manager
+    """
+
+    metadata: Optional[EnvironmentMetadata] = None
+    """
+    Metadata is data associated with this environment that's required for other
+    parts of Gitpod to function
+    """
+
+    spec: Optional[EnvironmentSpec] = None
+    """
+    Spec is the configuration of the environment that's required for the runner to
+    start the environment
+    """
+
+    status: Optional[EnvironmentStatus] = None
+    """Status is the current status of the environment"""
diff --git a/src/gitpod/types/environment_activity_signal.py b/src/gitpod/types/environment_activity_signal.py
new file mode 100644
index 0000000..afedc99
--- /dev/null
+++ b/src/gitpod/types/environment_activity_signal.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["EnvironmentActivitySignal"]
+
+
+class EnvironmentActivitySignal(BaseModel):
+    source: Optional[str] = None
+    """
+    source of the activity signal, such as "VS Code", "SSH", or "Automations". It
+    should be a human-readable string that describes the source of the activity
+    signal.
+    """
+
+    timestamp: Optional[datetime] = None
+    """
+    timestamp of when the activity was observed by the source. Only reported every 5
+    minutes. Zero value means no activity was observed.
+    """
diff --git a/src/gitpod/types/environment_activity_signal_param.py b/src/gitpod/types/environment_activity_signal_param.py
new file mode 100644
index 0000000..a9cb0c1
--- /dev/null
+++ b/src/gitpod/types/environment_activity_signal_param.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentActivitySignalParam"]
+
+
+class EnvironmentActivitySignalParam(TypedDict, total=False):
+    source: str
+    """
+    source of the activity signal, such as "VS Code", "SSH", or "Automations". It
+    should be a human-readable string that describes the source of the activity
+    signal.
+    """
+
+    timestamp: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+    """
+    timestamp of when the activity was observed by the source. Only reported every 5
+    minutes. Zero value means no activity was observed.
+    """
diff --git a/src/gitpod/types/environment_create_environment_token_params.py b/src/gitpod/types/environment_create_environment_token_params.py
new file mode 100644
index 0000000..cf7fdd1
--- /dev/null
+++ b/src/gitpod/types/environment_create_environment_token_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentCreateEnvironmentTokenParams"]
+
+
+class EnvironmentCreateEnvironmentTokenParams(TypedDict, total=False):
+    environment_id: Required[Annotated[str, PropertyInfo(alias="environmentId")]]
+    """
+    environment_id specifies the environment for which the access token should be
+    created.
+    """
diff --git a/src/gitpod/types/environment_create_environment_token_response.py b/src/gitpod/types/environment_create_environment_token_response.py
new file mode 100644
index 0000000..64b74d7
--- /dev/null
+++ b/src/gitpod/types/environment_create_environment_token_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["EnvironmentCreateEnvironmentTokenResponse"]
+
+
+class EnvironmentCreateEnvironmentTokenResponse(BaseModel):
+    access_token: str = FieldInfo(alias="accessToken")
+    """access_token is the token that can be used for environment authentication"""
diff --git a/src/gitpod/types/environment_create_from_project_params.py b/src/gitpod/types/environment_create_from_project_params.py
new file mode 100644
index 0000000..2c9806f
--- /dev/null
+++ b/src/gitpod/types/environment_create_from_project_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .environment_spec_param import EnvironmentSpecParam
+
+__all__ = ["EnvironmentCreateFromProjectParams"]
+
+
+class EnvironmentCreateFromProjectParams(TypedDict, total=False):
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+
+    spec: EnvironmentSpecParam
+    """
+    Spec is the configuration of the environment that's required for the runner to
+    start the environment Configuration already defined in the Project will override
+    parts of the spec, if set
+    """
diff --git a/src/gitpod/types/environment_create_from_project_response.py b/src/gitpod/types/environment_create_from_project_response.py
new file mode 100644
index 0000000..3390ce0
--- /dev/null
+++ b/src/gitpod/types/environment_create_from_project_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .environment import Environment
+
+__all__ = ["EnvironmentCreateFromProjectResponse"]
+
+
+class EnvironmentCreateFromProjectResponse(BaseModel):
+    environment: Environment
+    """+resource get environment"""
diff --git a/src/gitpod/types/environment_create_logs_token_params.py b/src/gitpod/types/environment_create_logs_token_params.py
new file mode 100644
index 0000000..ba9f17e
--- /dev/null
+++ b/src/gitpod/types/environment_create_logs_token_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentCreateLogsTokenParams"]
+
+
+class EnvironmentCreateLogsTokenParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """
+    environment_id specifies the environment for which the logs token should be
+    created.
+
+    +required
+    """
diff --git a/src/gitpod/types/environment_create_logs_token_response.py b/src/gitpod/types/environment_create_logs_token_response.py
new file mode 100644
index 0000000..c8a81b7
--- /dev/null
+++ b/src/gitpod/types/environment_create_logs_token_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["EnvironmentCreateLogsTokenResponse"]
+
+
+class EnvironmentCreateLogsTokenResponse(BaseModel):
+    access_token: str = FieldInfo(alias="accessToken")
+    """
+    access_token is the token that can be used to access the logs of the environment
+    """
diff --git a/src/gitpod/types/environment_create_params.py b/src/gitpod/types/environment_create_params.py
new file mode 100644
index 0000000..e49a7d2
--- /dev/null
+++ b/src/gitpod/types/environment_create_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from .environment_spec_param import EnvironmentSpecParam
+
+__all__ = ["EnvironmentCreateParams"]
+
+
+class EnvironmentCreateParams(TypedDict, total=False):
+    spec: EnvironmentSpecParam
+    """
+    spec is the configuration of the environment that's required for the to start
+    the environment
+    """
diff --git a/src/gitpod/types/environment_create_response.py b/src/gitpod/types/environment_create_response.py
new file mode 100644
index 0000000..e3c53f0
--- /dev/null
+++ b/src/gitpod/types/environment_create_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .environment import Environment
+
+__all__ = ["EnvironmentCreateResponse"]
+
+
+class EnvironmentCreateResponse(BaseModel):
+    environment: Environment
+    """+resource get environment"""
diff --git a/src/gitpod/types/environment_delete_params.py b/src/gitpod/types/environment_delete_params.py
new file mode 100644
index 0000000..20ce84f
--- /dev/null
+++ b/src/gitpod/types/environment_delete_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentDeleteParams"]
+
+
+class EnvironmentDeleteParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """environment_id specifies the environment that is going to delete.
+
+    +required
+    """
+
+    force: bool
+    """
+    force indicates whether the environment should be deleted forcefully When force
+    deleting an Environment, the Environment is removed immediately and environment
+    lifecycle is not respected. Force deleting can result in data loss on the
+    environment.
+    """
diff --git a/src/gitpod/types/environment_initializer.py b/src/gitpod/types/environment_initializer.py
new file mode 100644
index 0000000..2a2866a
--- /dev/null
+++ b/src/gitpod/types/environment_initializer.py
@@ -0,0 +1,52 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["EnvironmentInitializer", "Spec", "SpecContextURL", "SpecGit"]
+
+
+class SpecContextURL(BaseModel):
+    url: Optional[str] = None
+    """url is the URL from which the environment is created"""
+
+
+class SpecGit(BaseModel):
+    checkout_location: Optional[str] = FieldInfo(alias="checkoutLocation", default=None)
+    """
+    a path relative to the environment root in which the code will be checked out to
+    """
+
+    clone_target: Optional[str] = FieldInfo(alias="cloneTarget", default=None)
+    """the value for the clone target mode - use depends on the target mode"""
+
+    remote_uri: Optional[str] = FieldInfo(alias="remoteUri", default=None)
+    """remote_uri is the Git remote origin"""
+
+    target_mode: Optional[
+        Literal[
+            "CLONE_TARGET_MODE_UNSPECIFIED",
+            "CLONE_TARGET_MODE_REMOTE_HEAD",
+            "CLONE_TARGET_MODE_REMOTE_COMMIT",
+            "CLONE_TARGET_MODE_REMOTE_BRANCH",
+            "CLONE_TARGET_MODE_LOCAL_BRANCH",
+        ]
+    ] = FieldInfo(alias="targetMode", default=None)
+    """the target mode determines what gets checked out"""
+
+    upstream_remote_uri: Optional[str] = FieldInfo(alias="upstreamRemoteUri", default=None)
+    """upstream_Remote_uri is the fork upstream of a repository"""
+
+
+class Spec(BaseModel):
+    context_url: Optional[SpecContextURL] = FieldInfo(alias="contextUrl", default=None)
+
+    git: Optional[SpecGit] = None
+
+
+class EnvironmentInitializer(BaseModel):
+    specs: Optional[List[Spec]] = None
diff --git a/src/gitpod/types/environment_initializer_param.py b/src/gitpod/types/environment_initializer_param.py
new file mode 100644
index 0000000..695d836
--- /dev/null
+++ b/src/gitpod/types/environment_initializer_param.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentInitializerParam", "Spec", "SpecContextURL", "SpecGit"]
+
+
+class SpecContextURL(TypedDict, total=False):
+    url: str
+    """url is the URL from which the environment is created"""
+
+
+class SpecGit(TypedDict, total=False):
+    checkout_location: Annotated[str, PropertyInfo(alias="checkoutLocation")]
+    """
+    a path relative to the environment root in which the code will be checked out to
+    """
+
+    clone_target: Annotated[str, PropertyInfo(alias="cloneTarget")]
+    """the value for the clone target mode - use depends on the target mode"""
+
+    remote_uri: Annotated[str, PropertyInfo(alias="remoteUri")]
+    """remote_uri is the Git remote origin"""
+
+    target_mode: Annotated[
+        Literal[
+            "CLONE_TARGET_MODE_UNSPECIFIED",
+            "CLONE_TARGET_MODE_REMOTE_HEAD",
+            "CLONE_TARGET_MODE_REMOTE_COMMIT",
+            "CLONE_TARGET_MODE_REMOTE_BRANCH",
+            "CLONE_TARGET_MODE_LOCAL_BRANCH",
+        ],
+        PropertyInfo(alias="targetMode"),
+    ]
+    """the target mode determines what gets checked out"""
+
+    upstream_remote_uri: Annotated[str, PropertyInfo(alias="upstreamRemoteUri")]
+    """upstream_Remote_uri is the fork upstream of a repository"""
+
+
+class Spec(TypedDict, total=False):
+    context_url: Annotated[SpecContextURL, PropertyInfo(alias="contextUrl")]
+
+    git: SpecGit
+
+
+class EnvironmentInitializerParam(TypedDict, total=False):
+    specs: Iterable[Spec]
diff --git a/src/gitpod/types/environment_list_params.py b/src/gitpod/types/environment_list_params.py
new file mode 100644
index 0000000..eedde71
--- /dev/null
+++ b/src/gitpod/types/environment_list_params.py
@@ -0,0 +1,81 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .runner_kind import RunnerKind
+from .environment_phase import EnvironmentPhase
+
+__all__ = ["EnvironmentListParams", "Filter", "Pagination"]
+
+
+class EnvironmentListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing environments"""
+
+
+class Filter(TypedDict, total=False):
+    archival_status: Annotated[
+        Optional[
+            Literal[
+                "ARCHIVAL_STATUS_UNSPECIFIED",
+                "ARCHIVAL_STATUS_ACTIVE",
+                "ARCHIVAL_STATUS_ARCHIVED",
+                "ARCHIVAL_STATUS_ALL",
+            ]
+        ],
+        PropertyInfo(alias="archivalStatus"),
+    ]
+    """archival_status filters the response based on environment archive status"""
+
+    creator_ids: Annotated[List[str], PropertyInfo(alias="creatorIds")]
+    """
+    creator_ids filters the response to only Environments created by specified
+    members
+    """
+
+    project_ids: Annotated[List[str], PropertyInfo(alias="projectIds")]
+    """
+    project_ids filters the response to only Environments associated with the
+    specified projects
+    """
+
+    runner_ids: Annotated[List[str], PropertyInfo(alias="runnerIds")]
+    """
+    runner_ids filters the response to only Environments running on these Runner IDs
+    """
+
+    runner_kinds: Annotated[List[RunnerKind], PropertyInfo(alias="runnerKinds")]
+    """
+    runner_kinds filters the response to only Environments running on these Runner
+    Kinds
+    """
+
+    status_phases: Annotated[List[EnvironmentPhase], PropertyInfo(alias="statusPhases")]
+    """
+    actual_phases is a list of phases the environment must be in for it to be
+    returned in the API call
+    """
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/environment_mark_active_params.py b/src/gitpod/types/environment_mark_active_params.py
new file mode 100644
index 0000000..0e10971
--- /dev/null
+++ b/src/gitpod/types/environment_mark_active_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .environment_activity_signal_param import EnvironmentActivitySignalParam
+
+__all__ = ["EnvironmentMarkActiveParams"]
+
+
+class EnvironmentMarkActiveParams(TypedDict, total=False):
+    activity_signal: Annotated[EnvironmentActivitySignalParam, PropertyInfo(alias="activitySignal")]
+    """activity_signal specifies the activity."""
+
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """The ID of the environment to update activity for."""
diff --git a/src/gitpod/types/environment_metadata.py b/src/gitpod/types/environment_metadata.py
new file mode 100644
index 0000000..67090e0
--- /dev/null
+++ b/src/gitpod/types/environment_metadata.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.subject import Subject
+
+__all__ = ["EnvironmentMetadata"]
+
+
+class EnvironmentMetadata(BaseModel):
+    annotations: Optional[Dict[str, str]] = None
+    """
+    annotations are key/value pairs that gets attached to the environment.
+    +internal - not yet implemented
+    """
+
+    archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None)
+    """Time when the Environment was archived.
+
+    If not set, the environment is not archived.
+    """
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """Time when the Environment was created."""
+
+    creator: Optional[Subject] = None
+    """creator is the identity of the creator of the environment"""
+
+    last_started_at: Optional[datetime] = FieldInfo(alias="lastStartedAt", default=None)
+    """Time when the Environment was last started (i.e.
+
+    CreateEnvironment or StartEnvironment were called).
+    """
+
+    name: Optional[str] = None
+    """name is the name of the environment as specified by the user"""
+
+    organization_id: Optional[str] = FieldInfo(alias="organizationId", default=None)
+    """organization_id is the ID of the organization that contains the environment"""
+
+    original_context_url: Optional[str] = FieldInfo(alias="originalContextUrl", default=None)
+    """
+    original_context_url is the normalized URL from which the environment was
+    created
+    """
+
+    project_id: Optional[str] = FieldInfo(alias="projectId", default=None)
+    """
+    If the Environment was started from a project, the project_id will reference the
+    project.
+    """
+
+    runner_id: Optional[str] = FieldInfo(alias="runnerId", default=None)
+    """Runner is the ID of the runner that runs this environment."""
diff --git a/src/gitpod/types/environment_phase.py b/src/gitpod/types/environment_phase.py
new file mode 100644
index 0000000..817644d
--- /dev/null
+++ b/src/gitpod/types/environment_phase.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["EnvironmentPhase"]
+
+EnvironmentPhase: TypeAlias = Literal[
+    "ENVIRONMENT_PHASE_UNSPECIFIED",
+    "ENVIRONMENT_PHASE_CREATING",
+    "ENVIRONMENT_PHASE_STARTING",
+    "ENVIRONMENT_PHASE_RUNNING",
+    "ENVIRONMENT_PHASE_UPDATING",
+    "ENVIRONMENT_PHASE_STOPPING",
+    "ENVIRONMENT_PHASE_STOPPED",
+    "ENVIRONMENT_PHASE_DELETING",
+    "ENVIRONMENT_PHASE_DELETED",
+]
diff --git a/src/gitpod/types/environment_retrieve_params.py b/src/gitpod/types/environment_retrieve_params.py
new file mode 100644
index 0000000..f470b6c
--- /dev/null
+++ b/src/gitpod/types/environment_retrieve_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentRetrieveParams"]
+
+
+class EnvironmentRetrieveParams(TypedDict, total=False):
+    environment_id: Required[Annotated[str, PropertyInfo(alias="environmentId")]]
+    """environment_id specifies the environment to get"""
diff --git a/src/gitpod/types/environment_retrieve_response.py b/src/gitpod/types/environment_retrieve_response.py
new file mode 100644
index 0000000..1409d07
--- /dev/null
+++ b/src/gitpod/types/environment_retrieve_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .environment import Environment
+
+__all__ = ["EnvironmentRetrieveResponse"]
+
+
+class EnvironmentRetrieveResponse(BaseModel):
+    environment: Environment
+    """+resource get environment"""
diff --git a/src/gitpod/types/environment_spec.py b/src/gitpod/types/environment_spec.py
new file mode 100644
index 0000000..a6ccaa6
--- /dev/null
+++ b/src/gitpod/types/environment_spec.py
@@ -0,0 +1,186 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .admission_level import AdmissionLevel
+from .environment_phase import EnvironmentPhase
+from .environment_initializer import EnvironmentInitializer
+
+__all__ = [
+    "EnvironmentSpec",
+    "AutomationsFile",
+    "Content",
+    "Devcontainer",
+    "DevcontainerDotfiles",
+    "Machine",
+    "Port",
+    "Secret",
+    "SSHPublicKey",
+    "Timeout",
+]
+
+
+class AutomationsFile(BaseModel):
+    automations_file_path: Optional[str] = FieldInfo(alias="automationsFilePath", default=None)
+    """
+    automations_file_path is the path to the automations file that is applied in the
+    environment, relative to the repo root. path must not be absolute (start with a
+    /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    session: Optional[str] = None
+
+
+class Content(BaseModel):
+    git_email: Optional[str] = FieldInfo(alias="gitEmail", default=None)
+    """The Git email address"""
+
+    git_username: Optional[str] = FieldInfo(alias="gitUsername", default=None)
+    """The Git username"""
+
+    initializer: Optional[EnvironmentInitializer] = None
+    """initializer configures how the environment is to be initialized"""
+
+    session: Optional[str] = None
+
+
+class DevcontainerDotfiles(BaseModel):
+    repository: str
+    """URL of a dotfiles Git repository (e.g. https://github.com/owner/repository)"""
+
+
+class Devcontainer(BaseModel):
+    default_devcontainer_image: Optional[str] = FieldInfo(alias="defaultDevcontainerImage", default=None)
+    """
+    default_devcontainer_image is the default image that is used to start the
+    devcontainer if no devcontainer config file is found
+    """
+
+    devcontainer_file_path: Optional[str] = FieldInfo(alias="devcontainerFilePath", default=None)
+    """
+    devcontainer_file_path is the path to the devcontainer file relative to the repo
+    root path must not be absolute (start with a /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    dotfiles: Optional[DevcontainerDotfiles] = None
+    """Experimental: dotfiles is the dotfiles configuration of the devcontainer"""
+
+    session: Optional[str] = None
+
+
+class Machine(BaseModel):
+    class_: Optional[str] = FieldInfo(alias="class", default=None)
+    """Class denotes the class of the environment we ought to start"""
+
+    session: Optional[str] = None
+
+
+class Port(BaseModel):
+    admission: Optional[AdmissionLevel] = None
+    """policy of this port"""
+
+    name: Optional[str] = None
+    """name of this port"""
+
+    port: Optional[int] = None
+    """port number"""
+
+
+class Secret(BaseModel):
+    id: Optional[str] = None
+    """id is the unique identifier of the secret."""
+
+    container_registry_basic_auth_host: Optional[str] = FieldInfo(alias="containerRegistryBasicAuthHost", default=None)
+    """
+    container_registry_basic_auth_host is the hostname of the container registry
+    that supports basic auth
+    """
+
+    environment_variable: Optional[str] = FieldInfo(alias="environmentVariable", default=None)
+
+    file_path: Optional[str] = FieldInfo(alias="filePath", default=None)
+    """file_path is the path inside the devcontainer where the secret is mounted"""
+
+    git_credential_host: Optional[str] = FieldInfo(alias="gitCredentialHost", default=None)
+
+    name: Optional[str] = None
+    """name is the human readable description of the secret"""
+
+    session: Optional[str] = None
+    """
+    session indicated the current session of the secret. When the session does not
+    change, secrets are not reloaded in the environment.
+    """
+
+    source: Optional[str] = None
+    """source is the source of the secret, for now control-plane or runner"""
+
+    source_ref: Optional[str] = FieldInfo(alias="sourceRef", default=None)
+    """source_ref into the source, in case of control-plane this is uuid of the secret"""
+
+
+class SSHPublicKey(BaseModel):
+    id: Optional[str] = None
+    """id is the unique identifier of the public key"""
+
+    value: Optional[str] = None
+    """value is the actual public key in the public key file format"""
+
+
+class Timeout(BaseModel):
+    disconnected: Optional[str] = None
+    """
+    inacitivity is the maximum time of disconnection before the environment is
+    stopped or paused. Minimum duration is 30 minutes. Set to 0 to disable.
+    """
+
+
+class EnvironmentSpec(BaseModel):
+    admission: Optional[AdmissionLevel] = None
+    """admission controlls who can access the environment and its ports."""
+
+    automations_file: Optional[AutomationsFile] = FieldInfo(alias="automationsFile", default=None)
+    """automations_file is the automations file spec of the environment"""
+
+    content: Optional[Content] = None
+    """content is the content spec of the environment"""
+
+    desired_phase: Optional[EnvironmentPhase] = FieldInfo(alias="desiredPhase", default=None)
+    """Phase is the desired phase of the environment"""
+
+    devcontainer: Optional[Devcontainer] = None
+    """devcontainer is the devcontainer spec of the environment"""
+
+    machine: Optional[Machine] = None
+    """machine is the machine spec of the environment"""
+
+    ports: Optional[List[Port]] = None
+    """ports is the set of ports which ought to be exposed to the internet"""
+
+    secrets: Optional[List[Secret]] = None
+    """secrets are confidential data that is mounted into the environment"""
+
+    spec_version: Optional[str] = FieldInfo(alias="specVersion", default=None)
+    """version of the spec.
+
+    The value of this field has no semantic meaning (e.g. don't interpret it as as a
+    timestamp), but it can be used to impose a partial order. If a.spec_version <
+    b.spec_version then a was the spec before b.
+    """
+
+    ssh_public_keys: Optional[List[SSHPublicKey]] = FieldInfo(alias="sshPublicKeys", default=None)
+    """ssh_public_keys are the public keys used to ssh into the environment"""
+
+    timeout: Optional[Timeout] = None
+    """Timeout configures the environment timeout"""
diff --git a/src/gitpod/types/environment_spec_param.py b/src/gitpod/types/environment_spec_param.py
new file mode 100644
index 0000000..6e846a5
--- /dev/null
+++ b/src/gitpod/types/environment_spec_param.py
@@ -0,0 +1,193 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .admission_level import AdmissionLevel
+from .environment_phase import EnvironmentPhase
+from .environment_initializer_param import EnvironmentInitializerParam
+
+__all__ = [
+    "EnvironmentSpecParam",
+    "AutomationsFile",
+    "Content",
+    "Devcontainer",
+    "DevcontainerDotfiles",
+    "Machine",
+    "Port",
+    "Secret",
+    "SSHPublicKey",
+    "Timeout",
+]
+
+
+class AutomationsFile(TypedDict, total=False):
+    automations_file_path: Annotated[str, PropertyInfo(alias="automationsFilePath")]
+    """
+    automations_file_path is the path to the automations file that is applied in the
+    environment, relative to the repo root. path must not be absolute (start with a
+    /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    session: str
+
+
+class Content(TypedDict, total=False):
+    git_email: Annotated[str, PropertyInfo(alias="gitEmail")]
+    """The Git email address"""
+
+    git_username: Annotated[str, PropertyInfo(alias="gitUsername")]
+    """The Git username"""
+
+    initializer: EnvironmentInitializerParam
+    """initializer configures how the environment is to be initialized"""
+
+    session: str
+
+
+class DevcontainerDotfiles(TypedDict, total=False):
+    repository: Required[str]
+    """URL of a dotfiles Git repository (e.g. https://github.com/owner/repository)"""
+
+
+class Devcontainer(TypedDict, total=False):
+    default_devcontainer_image: Annotated[str, PropertyInfo(alias="defaultDevcontainerImage")]
+    """
+    default_devcontainer_image is the default image that is used to start the
+    devcontainer if no devcontainer config file is found
+    """
+
+    devcontainer_file_path: Annotated[str, PropertyInfo(alias="devcontainerFilePath")]
+    """
+    devcontainer_file_path is the path to the devcontainer file relative to the repo
+    root path must not be absolute (start with a /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    dotfiles: DevcontainerDotfiles
+    """Experimental: dotfiles is the dotfiles configuration of the devcontainer"""
+
+    session: str
+
+
+_MachineReservedKeywords = TypedDict(
+    "_MachineReservedKeywords",
+    {
+        "class": str,
+    },
+    total=False,
+)
+
+
+class Machine(_MachineReservedKeywords, total=False):
+    session: str
+
+
+class Port(TypedDict, total=False):
+    admission: AdmissionLevel
+    """policy of this port"""
+
+    name: str
+    """name of this port"""
+
+    port: int
+    """port number"""
+
+
+class Secret(TypedDict, total=False):
+    id: str
+    """id is the unique identifier of the secret."""
+
+    container_registry_basic_auth_host: Annotated[str, PropertyInfo(alias="containerRegistryBasicAuthHost")]
+    """
+    container_registry_basic_auth_host is the hostname of the container registry
+    that supports basic auth
+    """
+
+    environment_variable: Annotated[str, PropertyInfo(alias="environmentVariable")]
+
+    file_path: Annotated[str, PropertyInfo(alias="filePath")]
+    """file_path is the path inside the devcontainer where the secret is mounted"""
+
+    git_credential_host: Annotated[str, PropertyInfo(alias="gitCredentialHost")]
+
+    name: str
+    """name is the human readable description of the secret"""
+
+    session: str
+    """
+    session indicated the current session of the secret. When the session does not
+    change, secrets are not reloaded in the environment.
+    """
+
+    source: str
+    """source is the source of the secret, for now control-plane or runner"""
+
+    source_ref: Annotated[str, PropertyInfo(alias="sourceRef")]
+    """source_ref into the source, in case of control-plane this is uuid of the secret"""
+
+
+class SSHPublicKey(TypedDict, total=False):
+    id: str
+    """id is the unique identifier of the public key"""
+
+    value: str
+    """value is the actual public key in the public key file format"""
+
+
+class Timeout(TypedDict, total=False):
+    disconnected: str
+    """
+    inacitivity is the maximum time of disconnection before the environment is
+    stopped or paused. Minimum duration is 30 minutes. Set to 0 to disable.
+    """
+
+
+class EnvironmentSpecParam(TypedDict, total=False):
+    admission: AdmissionLevel
+    """admission controlls who can access the environment and its ports."""
+
+    automations_file: Annotated[AutomationsFile, PropertyInfo(alias="automationsFile")]
+    """automations_file is the automations file spec of the environment"""
+
+    content: Content
+    """content is the content spec of the environment"""
+
+    desired_phase: Annotated[EnvironmentPhase, PropertyInfo(alias="desiredPhase")]
+    """Phase is the desired phase of the environment"""
+
+    devcontainer: Devcontainer
+    """devcontainer is the devcontainer spec of the environment"""
+
+    machine: Machine
+    """machine is the machine spec of the environment"""
+
+    ports: Iterable[Port]
+    """ports is the set of ports which ought to be exposed to the internet"""
+
+    secrets: Iterable[Secret]
+    """secrets are confidential data that is mounted into the environment"""
+
+    spec_version: Annotated[str, PropertyInfo(alias="specVersion")]
+    """version of the spec.
+
+    The value of this field has no semantic meaning (e.g. don't interpret it as as a
+    timestamp), but it can be used to impose a partial order. If a.spec_version <
+    b.spec_version then a was the spec before b.
+    """
+
+    ssh_public_keys: Annotated[Iterable[SSHPublicKey], PropertyInfo(alias="sshPublicKeys")]
+    """ssh_public_keys are the public keys used to ssh into the environment"""
+
+    timeout: Timeout
+    """Timeout configures the environment timeout"""
diff --git a/src/gitpod/types/environment_start_params.py b/src/gitpod/types/environment_start_params.py
new file mode 100644
index 0000000..83fe0a4
--- /dev/null
+++ b/src/gitpod/types/environment_start_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentStartParams"]
+
+
+class EnvironmentStartParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """environment_id specifies which environment should be started."""
diff --git a/src/gitpod/types/environment_status.py b/src/gitpod/types/environment_status.py
new file mode 100644
index 0000000..4595543
--- /dev/null
+++ b/src/gitpod/types/environment_status.py
@@ -0,0 +1,412 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .environment_phase import EnvironmentPhase
+from .environment_activity_signal import EnvironmentActivitySignal
+
+__all__ = [
+    "EnvironmentStatus",
+    "AutomationsFile",
+    "Content",
+    "ContentGit",
+    "ContentGitChangedFile",
+    "Devcontainer",
+    "EnvironmentURLs",
+    "EnvironmentURLsPort",
+    "EnvironmentURLsSSH",
+    "Machine",
+    "MachineVersions",
+    "RunnerAck",
+    "Secret",
+    "SSHPublicKey",
+]
+
+
+class AutomationsFile(BaseModel):
+    automations_file_path: Optional[str] = FieldInfo(alias="automationsFilePath", default=None)
+    """
+    automations_file_path is the path to the automations file relative to the repo
+    root.
+    """
+
+    automations_file_presence: Optional[
+        Literal["PRESENCE_UNSPECIFIED", "PRESENCE_ABSENT", "PRESENCE_DISCOVERED", "PRESENCE_SPECIFIED"]
+    ] = FieldInfo(alias="automationsFilePresence", default=None)
+    """
+    automations_file_presence indicates how an automations file is present in the
+    environment.
+    """
+
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """
+    failure_message contains the reason the automations file failed to be applied.
+    This is only set if the phase is FAILED.
+    """
+
+    phase: Optional[
+        Literal[
+            "CONTENT_PHASE_UNSPECIFIED",
+            "CONTENT_PHASE_CREATING",
+            "CONTENT_PHASE_INITIALIZING",
+            "CONTENT_PHASE_READY",
+            "CONTENT_PHASE_UPDATING",
+            "CONTENT_PHASE_FAILED",
+        ]
+    ] = None
+    """phase is the current phase of the automations file."""
+
+    session: Optional[str] = None
+    """
+    session is the automations file session that is currently applied in the
+    environment.
+    """
+
+    warning_message: Optional[str] = FieldInfo(alias="warningMessage", default=None)
+    """warning_message contains warnings, e.g.
+
+    when no triggers are defined in the automations file.
+    """
+
+
+class ContentGitChangedFile(BaseModel):
+    change_type: Optional[
+        Literal[
+            "CHANGE_TYPE_UNSPECIFIED",
+            "CHANGE_TYPE_ADDED",
+            "CHANGE_TYPE_MODIFIED",
+            "CHANGE_TYPE_DELETED",
+            "CHANGE_TYPE_RENAMED",
+            "CHANGE_TYPE_COPIED",
+            "CHANGE_TYPE_UPDATED_BUT_UNMERGED",
+            "CHANGE_TYPE_UNTRACKED",
+        ]
+    ] = FieldInfo(alias="changeType", default=None)
+    """ChangeType is the type of change that happened to the file"""
+
+    path: Optional[str] = None
+    """path is the path of the file"""
+
+
+class ContentGit(BaseModel):
+    branch: Optional[str] = None
+    """branch is branch we're currently on"""
+
+    changed_files: Optional[List[ContentGitChangedFile]] = FieldInfo(alias="changedFiles", default=None)
+    """
+    changed_files is an array of changed files in the environment, possibly
+    truncated
+    """
+
+    clone_url: Optional[str] = FieldInfo(alias="cloneUrl", default=None)
+    """
+    clone_url is the repository url as you would pass it to "git clone". Only HTTPS
+    clone URLs are supported.
+    """
+
+    latest_commit: Optional[str] = FieldInfo(alias="latestCommit", default=None)
+    """latest_commit is the most recent commit on the current branch"""
+
+    total_changed_files: Optional[int] = FieldInfo(alias="totalChangedFiles", default=None)
+
+    total_unpushed_commits: Optional[int] = FieldInfo(alias="totalUnpushedCommits", default=None)
+    """the total number of unpushed changes"""
+
+    unpushed_commits: Optional[List[str]] = FieldInfo(alias="unpushedCommits", default=None)
+    """
+    unpushed_commits is an array of unpushed changes in the environment, possibly
+    truncated
+    """
+
+
+class Content(BaseModel):
+    content_location_in_machine: Optional[str] = FieldInfo(alias="contentLocationInMachine", default=None)
+    """content_location_in_machine is the location of the content in the machine"""
+
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message contains the reason the content initialization failed."""
+
+    git: Optional[ContentGit] = None
+    """
+    git is the Git working copy status of the environment. Note: this is a
+    best-effort field and more often than not will not be present. Its absence does
+    not indicate the absence of a working copy.
+    """
+
+    phase: Optional[
+        Literal[
+            "CONTENT_PHASE_UNSPECIFIED",
+            "CONTENT_PHASE_CREATING",
+            "CONTENT_PHASE_INITIALIZING",
+            "CONTENT_PHASE_READY",
+            "CONTENT_PHASE_UPDATING",
+            "CONTENT_PHASE_FAILED",
+        ]
+    ] = None
+    """phase is the current phase of the environment content"""
+
+    session: Optional[str] = None
+    """session is the session that is currently active in the environment."""
+
+    warning_message: Optional[str] = FieldInfo(alias="warningMessage", default=None)
+    """warning_message contains warnings, e.g.
+
+    when the content is present but not in the expected state.
+    """
+
+
+class Devcontainer(BaseModel):
+    container_id: Optional[str] = FieldInfo(alias="containerId", default=None)
+    """container_id is the ID of the container."""
+
+    container_name: Optional[str] = FieldInfo(alias="containerName", default=None)
+    """
+    container_name is the name of the container that is used to connect to the
+    devcontainer
+    """
+
+    devcontainerconfig_in_sync: Optional[bool] = FieldInfo(alias="devcontainerconfigInSync", default=None)
+    """devcontainerconfig_in_sync indicates if the devcontainer is up to date w.r.t.
+
+    the devcontainer config file.
+    """
+
+    devcontainer_file_path: Optional[str] = FieldInfo(alias="devcontainerFilePath", default=None)
+    """
+    devcontainer_file_path is the path to the devcontainer file relative to the repo
+    root
+    """
+
+    devcontainer_file_presence: Optional[
+        Literal["PRESENCE_UNSPECIFIED", "PRESENCE_GENERATED", "PRESENCE_DISCOVERED", "PRESENCE_SPECIFIED"]
+    ] = FieldInfo(alias="devcontainerFilePresence", default=None)
+    """
+    devcontainer_file_presence indicates how the devcontainer file is present in the
+    repo.
+    """
+
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message contains the reason the devcontainer failed to operate."""
+
+    phase: Optional[
+        Literal["PHASE_UNSPECIFIED", "PHASE_CREATING", "PHASE_RUNNING", "PHASE_STOPPED", "PHASE_FAILED"]
+    ] = None
+    """phase is the current phase of the devcontainer"""
+
+    remote_user: Optional[str] = FieldInfo(alias="remoteUser", default=None)
+    """remote_user is the user that is used to connect to the devcontainer"""
+
+    remote_workspace_folder: Optional[str] = FieldInfo(alias="remoteWorkspaceFolder", default=None)
+    """
+    remote_workspace_folder is the folder that is used to connect to the
+    devcontainer
+    """
+
+    secrets_in_sync: Optional[bool] = FieldInfo(alias="secretsInSync", default=None)
+    """secrets_in_sync indicates if the secrets are up to date w.r.t.
+
+    the running devcontainer.
+    """
+
+    session: Optional[str] = None
+    """session is the session that is currently active in the devcontainer."""
+
+    warning_message: Optional[str] = FieldInfo(alias="warningMessage", default=None)
+    """warning_message contains warnings, e.g.
+
+    when the devcontainer is present but not in the expected state.
+    """
+
+
+class EnvironmentURLsPort(BaseModel):
+    port: Optional[int] = None
+    """port is the port number of the environment port"""
+
+    url: Optional[str] = None
+    """url is the URL at which the environment port can be accessed"""
+
+
+class EnvironmentURLsSSH(BaseModel):
+    url: Optional[str] = None
+
+
+class EnvironmentURLs(BaseModel):
+    logs: Optional[str] = None
+    """logs is the URL at which the environment logs can be accessed."""
+
+    ports: Optional[List[EnvironmentURLsPort]] = None
+
+    ssh: Optional[EnvironmentURLsSSH] = None
+    """SSH is the URL at which the environment can be accessed via SSH."""
+
+
+class MachineVersions(BaseModel):
+    supervisor_commit: Optional[str] = FieldInfo(alias="supervisorCommit", default=None)
+
+    supervisor_version: Optional[str] = FieldInfo(alias="supervisorVersion", default=None)
+
+
+class Machine(BaseModel):
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message contains the reason the machine failed to operate."""
+
+    phase: Optional[
+        Literal[
+            "PHASE_UNSPECIFIED",
+            "PHASE_CREATING",
+            "PHASE_STARTING",
+            "PHASE_RUNNING",
+            "PHASE_STOPPING",
+            "PHASE_STOPPED",
+            "PHASE_DELETING",
+            "PHASE_DELETED",
+        ]
+    ] = None
+    """phase is the current phase of the environment machine"""
+
+    session: Optional[str] = None
+    """session is the session that is currently active in the machine."""
+
+    timeout: Optional[str] = None
+    """timeout contains the reason the environment has timed out.
+
+    If this field is empty, the environment has not timed out.
+    """
+
+    versions: Optional[MachineVersions] = None
+    """versions contains the versions of components in the machine."""
+
+    warning_message: Optional[str] = FieldInfo(alias="warningMessage", default=None)
+    """warning_message contains warnings, e.g.
+
+    when the machine is present but not in the expected state.
+    """
+
+
+class RunnerAck(BaseModel):
+    message: Optional[str] = None
+
+    spec_version: Optional[str] = FieldInfo(alias="specVersion", default=None)
+
+    status_code: Optional[
+        Literal[
+            "STATUS_CODE_UNSPECIFIED",
+            "STATUS_CODE_OK",
+            "STATUS_CODE_INVALID_RESOURCE",
+            "STATUS_CODE_FAILED_PRECONDITION",
+        ]
+    ] = FieldInfo(alias="statusCode", default=None)
+
+
+class Secret(BaseModel):
+    id: Optional[str] = None
+    """id is the unique identifier of the secret."""
+
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message contains the reason the secret failed to be materialize."""
+
+    phase: Optional[
+        Literal[
+            "CONTENT_PHASE_UNSPECIFIED",
+            "CONTENT_PHASE_CREATING",
+            "CONTENT_PHASE_INITIALIZING",
+            "CONTENT_PHASE_READY",
+            "CONTENT_PHASE_UPDATING",
+            "CONTENT_PHASE_FAILED",
+        ]
+    ] = None
+
+    secret_name: Optional[str] = FieldInfo(alias="secretName", default=None)
+
+    session: Optional[str] = None
+    """session is the session that is currently active in the environment."""
+
+    warning_message: Optional[str] = FieldInfo(alias="warningMessage", default=None)
+    """warning_message contains warnings, e.g.
+
+    when the secret is present but not in the expected state.
+    """
+
+
+class SSHPublicKey(BaseModel):
+    id: Optional[str] = None
+    """id is the unique identifier of the public key"""
+
+    phase: Optional[
+        Literal[
+            "CONTENT_PHASE_UNSPECIFIED",
+            "CONTENT_PHASE_CREATING",
+            "CONTENT_PHASE_INITIALIZING",
+            "CONTENT_PHASE_READY",
+            "CONTENT_PHASE_UPDATING",
+            "CONTENT_PHASE_FAILED",
+        ]
+    ] = None
+    """phase is the current phase of the public key"""
+
+
+class EnvironmentStatus(BaseModel):
+    activity_signal: Optional[EnvironmentActivitySignal] = FieldInfo(alias="activitySignal", default=None)
+    """activity_signal is the last activity signal for the environment."""
+
+    automations_file: Optional[AutomationsFile] = FieldInfo(alias="automationsFile", default=None)
+    """automations_file contains the status of the automations file."""
+
+    content: Optional[Content] = None
+    """content contains the status of the environment content."""
+
+    devcontainer: Optional[Devcontainer] = None
+    """devcontainer contains the status of the devcontainer."""
+
+    environment_urls: Optional[EnvironmentURLs] = FieldInfo(alias="environmentUrls", default=None)
+    """
+    environment_url contains the URL at which the environment can be accessed. This
+    field is only set if the environment is running.
+    """
+
+    failure_message: Optional[List[str]] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message summarises why the environment failed to operate.
+
+    If this is non-empty the environment has failed to operate and will likely
+    transition to a stopped state.
+    """
+
+    machine: Optional[Machine] = None
+    """machine contains the status of the environment machine"""
+
+    phase: Optional[EnvironmentPhase] = None
+    """
+    the phase of an environment is a simple, high-level summary of where the
+    environment is in its lifecycle
+    """
+
+    runner_ack: Optional[RunnerAck] = FieldInfo(alias="runnerAck", default=None)
+    """
+    runner_ack contains the acknowledgement from the runner that is has received the
+    environment spec.
+    """
+
+    secrets: Optional[List[Secret]] = None
+    """secrets contains the status of the environment secrets"""
+
+    ssh_public_keys: Optional[List[SSHPublicKey]] = FieldInfo(alias="sshPublicKeys", default=None)
+    """ssh_public_keys contains the status of the environment ssh public keys"""
+
+    status_version: Optional[str] = FieldInfo(alias="statusVersion", default=None)
+    """version of the status update.
+
+    Environment instances themselves are unversioned, but their status has different
+    versions. The value of this field has no semantic meaning (e.g. don't interpret
+    it as as a timestamp), but it can be used to impose a partial order. If
+    a.status_version < b.status_version then a was the status before b.
+    """
+
+    warning_message: Optional[List[str]] = FieldInfo(alias="warningMessage", default=None)
+    """warning_message contains warnings, e.g.
+
+    when the environment is present but not in the expected state.
+    """
diff --git a/src/gitpod/types/environment_stop_params.py b/src/gitpod/types/environment_stop_params.py
new file mode 100644
index 0000000..885db41
--- /dev/null
+++ b/src/gitpod/types/environment_stop_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentStopParams"]
+
+
+class EnvironmentStopParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """environment_id specifies which environment should be stopped.
+
+    +required
+    """
diff --git a/src/gitpod/types/environment_unarchive_params.py b/src/gitpod/types/environment_unarchive_params.py
new file mode 100644
index 0000000..862c3be
--- /dev/null
+++ b/src/gitpod/types/environment_unarchive_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EnvironmentUnarchiveParams"]
+
+
+class EnvironmentUnarchiveParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """environment_id specifies the environment to unarchive.
+
+    +required
+    """
diff --git a/src/gitpod/types/environment_update_params.py b/src/gitpod/types/environment_update_params.py
new file mode 100644
index 0000000..60d7bfc
--- /dev/null
+++ b/src/gitpod/types/environment_update_params.py
@@ -0,0 +1,134 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .admission_level import AdmissionLevel
+from .environment_initializer_param import EnvironmentInitializerParam
+
+__all__ = [
+    "EnvironmentUpdateParams",
+    "Metadata",
+    "Spec",
+    "SpecAutomationsFile",
+    "SpecContent",
+    "SpecDevcontainer",
+    "SpecPort",
+    "SpecSSHPublicKey",
+    "SpecTimeout",
+]
+
+
+class EnvironmentUpdateParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """environment_id specifies which environment should be updated.
+
+    +required
+    """
+
+    metadata: Optional[Metadata]
+
+    spec: Optional[Spec]
+
+
+class Metadata(TypedDict, total=False):
+    name: Optional[str]
+    """name is the user-defined display name of the environment"""
+
+
+class SpecAutomationsFile(TypedDict, total=False):
+    automations_file_path: Annotated[Optional[str], PropertyInfo(alias="automationsFilePath")]
+    """
+    automations_file_path is the path to the automations file that is applied in the
+    environment, relative to the repo root. path must not be absolute (start with a
+    /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    session: Optional[str]
+
+
+class SpecContent(TypedDict, total=False):
+    git_email: Annotated[Optional[str], PropertyInfo(alias="gitEmail")]
+    """The Git email address"""
+
+    git_username: Annotated[Optional[str], PropertyInfo(alias="gitUsername")]
+    """The Git username"""
+
+    initializer: Optional[EnvironmentInitializerParam]
+    """initializer configures how the environment is to be initialized"""
+
+    session: Optional[str]
+    """session should be changed to trigger a content reinitialization"""
+
+
+class SpecDevcontainer(TypedDict, total=False):
+    devcontainer_file_path: Annotated[Optional[str], PropertyInfo(alias="devcontainerFilePath")]
+    """
+    devcontainer_file_path is the path to the devcontainer file relative to the repo
+    root path must not be absolute (start with a /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    session: Optional[str]
+    """session should be changed to trigger a devcontainer rebuild"""
+
+
+class SpecPort(TypedDict, total=False):
+    admission: AdmissionLevel
+    """policy of this port"""
+
+    name: str
+    """name of this port"""
+
+    port: int
+    """port number"""
+
+
+class SpecSSHPublicKey(TypedDict, total=False):
+    id: str
+    """id is the unique identifier of the public key"""
+
+    value: Optional[str]
+    """
+    value is the actual public key in the public key file format if not provided,
+    the public key will be removed
+    """
+
+
+class SpecTimeout(TypedDict, total=False):
+    disconnected: Optional[str]
+    """
+    inacitivity is the maximum time of disconnection before the environment is
+    stopped or paused. Minimum duration is 30 minutes. Set to 0 to disable.
+    """
+
+
+class Spec(TypedDict, total=False):
+    automations_file: Annotated[Optional[SpecAutomationsFile], PropertyInfo(alias="automationsFile")]
+    """automations_file is the automations file spec of the environment"""
+
+    content: Optional[SpecContent]
+
+    devcontainer: Optional[SpecDevcontainer]
+
+    ports: Iterable[SpecPort]
+    """ports controls port sharing"""
+
+    ssh_public_keys: Annotated[Iterable[SpecSSHPublicKey], PropertyInfo(alias="sshPublicKeys")]
+    """
+    ssh_public_keys are the public keys to update empty array means nothing to
+    update
+    """
+
+    timeout: Optional[SpecTimeout]
+    """Timeout configures the environment timeout"""
diff --git a/src/gitpod/types/environment_usage_record.py b/src/gitpod/types/environment_usage_record.py
new file mode 100644
index 0000000..59c85a7
--- /dev/null
+++ b/src/gitpod/types/environment_usage_record.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["EnvironmentUsageRecord"]
+
+
+class EnvironmentUsageRecord(BaseModel):
+    id: Optional[str] = None
+    """Environment usage record ID."""
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """Time when the environment was created."""
+
+    environment_class_id: Optional[str] = FieldInfo(alias="environmentClassId", default=None)
+    """Environment class ID associated with the record."""
+
+    environment_id: Optional[str] = FieldInfo(alias="environmentId", default=None)
+    """Environment ID associated with the record."""
+
+    project_id: Optional[str] = FieldInfo(alias="projectId", default=None)
+    """Project ID associated with the environment (if available)."""
+
+    runner_id: Optional[str] = FieldInfo(alias="runnerId", default=None)
+    """Runner ID associated with the environment."""
+
+    stopped_at: Optional[datetime] = FieldInfo(alias="stoppedAt", default=None)
+    """Time when the environment was stopped."""
+
+    user_id: Optional[str] = FieldInfo(alias="userId", default=None)
+    """
+    User ID is the ID of the user who created the environment associated with the
+    record.
+    """
diff --git a/src/gitpod/types/environments/__init__.py b/src/gitpod/types/environments/__init__.py
new file mode 100644
index 0000000..2a0fe22
--- /dev/null
+++ b/src/gitpod/types/environments/__init__.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .class_list_params import ClassListParams as ClassListParams
+from .automations_file_param import AutomationsFileParam as AutomationsFileParam
+from .automation_upsert_params import AutomationUpsertParams as AutomationUpsertParams
+from .automation_upsert_response import AutomationUpsertResponse as AutomationUpsertResponse
diff --git a/src/gitpod/types/environments/automation_upsert_params.py b/src/gitpod/types/environments/automation_upsert_params.py
new file mode 100644
index 0000000..754ae89
--- /dev/null
+++ b/src/gitpod/types/environments/automation_upsert_params.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .automations_file_param import AutomationsFileParam
+
+__all__ = ["AutomationUpsertParams"]
+
+
+class AutomationUpsertParams(TypedDict, total=False):
+    automations_file: Annotated[AutomationsFileParam, PropertyInfo(alias="automationsFile")]
+    """
+    WARN: Do not remove any field here, as it will break reading automation yaml
+    files. We error if there are any unknown fields in the yaml (to ensure the yaml
+    is correct), but would break if we removed any fields. This includes marking a
+    field as "reserved" in the proto file, this will also break reading the yaml.
+    """
+
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
diff --git a/src/gitpod/types/environments/automation_upsert_response.py b/src/gitpod/types/environments/automation_upsert_response.py
new file mode 100644
index 0000000..f01ae1b
--- /dev/null
+++ b/src/gitpod/types/environments/automation_upsert_response.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["AutomationUpsertResponse"]
+
+
+class AutomationUpsertResponse(BaseModel):
+    updated_service_ids: Optional[List[str]] = FieldInfo(alias="updatedServiceIds", default=None)
+
+    updated_task_ids: Optional[List[str]] = FieldInfo(alias="updatedTaskIds", default=None)
diff --git a/src/gitpod/types/environments/automations/__init__.py b/src/gitpod/types/environments/automations/__init__.py
new file mode 100644
index 0000000..d6c647a
--- /dev/null
+++ b/src/gitpod/types/environments/automations/__init__.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .service import Service as Service
+from .service_spec import ServiceSpec as ServiceSpec
+from .service_phase import ServicePhase as ServicePhase
+from .service_status import ServiceStatus as ServiceStatus
+from .service_metadata import ServiceMetadata as ServiceMetadata
+from .task_list_params import TaskListParams as TaskListParams
+from .task_start_params import TaskStartParams as TaskStartParams
+from .service_spec_param import ServiceSpecParam as ServiceSpecParam
+from .task_create_params import TaskCreateParams as TaskCreateParams
+from .task_delete_params import TaskDeleteParams as TaskDeleteParams
+from .task_update_params import TaskUpdateParams as TaskUpdateParams
+from .service_list_params import ServiceListParams as ServiceListParams
+from .service_stop_params import ServiceStopParams as ServiceStopParams
+from .task_start_response import TaskStartResponse as TaskStartResponse
+from .service_start_params import ServiceStartParams as ServiceStartParams
+from .task_create_response import TaskCreateResponse as TaskCreateResponse
+from .task_retrieve_params import TaskRetrieveParams as TaskRetrieveParams
+from .service_create_params import ServiceCreateParams as ServiceCreateParams
+from .service_delete_params import ServiceDeleteParams as ServiceDeleteParams
+from .service_update_params import ServiceUpdateParams as ServiceUpdateParams
+from .service_metadata_param import ServiceMetadataParam as ServiceMetadataParam
+from .task_retrieve_response import TaskRetrieveResponse as TaskRetrieveResponse
+from .service_create_response import ServiceCreateResponse as ServiceCreateResponse
+from .service_retrieve_params import ServiceRetrieveParams as ServiceRetrieveParams
+from .service_retrieve_response import ServiceRetrieveResponse as ServiceRetrieveResponse
diff --git a/src/gitpod/types/environments/automations/service.py b/src/gitpod/types/environments/automations/service.py
new file mode 100644
index 0000000..9dbb5aa
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from .service_spec import ServiceSpec
+from .service_status import ServiceStatus
+from .service_metadata import ServiceMetadata
+
+__all__ = ["Service"]
+
+
+class Service(BaseModel):
+    id: str
+
+    environment_id: Optional[str] = FieldInfo(alias="environmentId", default=None)
+
+    metadata: Optional[ServiceMetadata] = None
+
+    spec: Optional[ServiceSpec] = None
+
+    status: Optional[ServiceStatus] = None
diff --git a/src/gitpod/types/environments/automations/service_create_params.py b/src/gitpod/types/environments/automations/service_create_params.py
new file mode 100644
index 0000000..248cd55
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_create_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from .service_spec_param import ServiceSpecParam
+from .service_metadata_param import ServiceMetadataParam
+
+__all__ = ["ServiceCreateParams"]
+
+
+class ServiceCreateParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+
+    metadata: ServiceMetadataParam
+
+    spec: ServiceSpecParam
diff --git a/src/gitpod/types/environments/automations/service_create_response.py b/src/gitpod/types/environments/automations/service_create_response.py
new file mode 100644
index 0000000..0aa85db
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_create_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .service import Service
+from ...._models import BaseModel
+
+__all__ = ["ServiceCreateResponse"]
+
+
+class ServiceCreateResponse(BaseModel):
+    service: Service
diff --git a/src/gitpod/types/environments/automations/service_delete_params.py b/src/gitpod/types/environments/automations/service_delete_params.py
new file mode 100644
index 0000000..2fc5f3d
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_delete_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ServiceDeleteParams"]
+
+
+class ServiceDeleteParams(TypedDict, total=False):
+    id: str
+
+    force: bool
diff --git a/src/gitpod/types/environments/automations/service_list_params.py b/src/gitpod/types/environments/automations/service_list_params.py
new file mode 100644
index 0000000..c2c08b0
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_list_params.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["ServiceListParams", "Filter", "Pagination"]
+
+
+class ServiceListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+    """filter contains the filter options for listing services"""
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing services"""
+
+
+class Filter(TypedDict, total=False):
+    environment_ids: Annotated[List[str], PropertyInfo(alias="environmentIds")]
+    """environment_ids filters the response to only services of these environments"""
+
+    references: List[str]
+    """references filters the response to only services with these references"""
+
+    service_ids: Annotated[List[str], PropertyInfo(alias="serviceIds")]
+    """service_ids filters the response to only services with these IDs"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/environments/automations/service_metadata.py b/src/gitpod/types/environments/automations/service_metadata.py
new file mode 100644
index 0000000..4555022
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_metadata.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from ...shared.subject import Subject
+from ...shared.automation_trigger import AutomationTrigger
+
+__all__ = ["ServiceMetadata"]
+
+
+class ServiceMetadata(BaseModel):
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """created_at is the time the service was created."""
+
+    creator: Optional[Subject] = None
+    """creator describes the principal who created the service."""
+
+    description: Optional[str] = None
+    """description is a user-facing description for the service.
+
+    It can be used to provide context and documentation for the service.
+    """
+
+    name: Optional[str] = None
+    """name is a user-facing name for the service.
+
+    Unlike the reference, this field is not unique, and not referenced by the
+    system. This is a short descriptive name for the service.
+    """
+
+    reference: Optional[str] = None
+    """
+    reference is a user-facing identifier for the service which must be unique on
+    the environment. It is used to express dependencies between services, and to
+    identify the service in user interactions (e.g. the CLI).
+    """
+
+    triggered_by: Optional[List[AutomationTrigger]] = FieldInfo(alias="triggeredBy", default=None)
+    """triggered_by is a list of trigger that start the service."""
diff --git a/src/gitpod/types/environments/automations/service_metadata_param.py b/src/gitpod/types/environments/automations/service_metadata_param.py
new file mode 100644
index 0000000..669b250
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_metadata_param.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable
+from datetime import datetime
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from ...shared_params.subject import Subject
+from ...shared_params.automation_trigger import AutomationTrigger
+
+__all__ = ["ServiceMetadataParam"]
+
+
+class ServiceMetadataParam(TypedDict, total=False):
+    created_at: Annotated[Union[str, datetime], PropertyInfo(alias="createdAt", format="iso8601")]
+    """created_at is the time the service was created."""
+
+    creator: Subject
+    """creator describes the principal who created the service."""
+
+    description: str
+    """description is a user-facing description for the service.
+
+    It can be used to provide context and documentation for the service.
+    """
+
+    name: str
+    """name is a user-facing name for the service.
+
+    Unlike the reference, this field is not unique, and not referenced by the
+    system. This is a short descriptive name for the service.
+    """
+
+    reference: str
+    """
+    reference is a user-facing identifier for the service which must be unique on
+    the environment. It is used to express dependencies between services, and to
+    identify the service in user interactions (e.g. the CLI).
+    """
+
+    triggered_by: Annotated[Iterable[AutomationTrigger], PropertyInfo(alias="triggeredBy")]
+    """triggered_by is a list of trigger that start the service."""
diff --git a/src/gitpod/types/environments/automations/service_phase.py b/src/gitpod/types/environments/automations/service_phase.py
new file mode 100644
index 0000000..df4c820
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_phase.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ServicePhase"]
+
+ServicePhase: TypeAlias = Literal[
+    "SERVICE_PHASE_UNSPECIFIED",
+    "SERVICE_PHASE_STARTING",
+    "SERVICE_PHASE_RUNNING",
+    "SERVICE_PHASE_STOPPING",
+    "SERVICE_PHASE_STOPPED",
+    "SERVICE_PHASE_FAILED",
+    "SERVICE_PHASE_DELETED",
+]
diff --git a/src/gitpod/types/environments/automations/service_retrieve_params.py b/src/gitpod/types/environments/automations/service_retrieve_params.py
new file mode 100644
index 0000000..857888a
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_retrieve_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ServiceRetrieveParams"]
+
+
+class ServiceRetrieveParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations/service_retrieve_response.py b/src/gitpod/types/environments/automations/service_retrieve_response.py
new file mode 100644
index 0000000..e377837
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_retrieve_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .service import Service
+from ...._models import BaseModel
+
+__all__ = ["ServiceRetrieveResponse"]
+
+
+class ServiceRetrieveResponse(BaseModel):
+    service: Service
diff --git a/src/gitpod/types/environments/automations/service_spec.py b/src/gitpod/types/environments/automations/service_spec.py
new file mode 100644
index 0000000..7e160fe
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_spec.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from .service_phase import ServicePhase
+from ...shared.runs_on import RunsOn
+
+__all__ = ["ServiceSpec", "Commands"]
+
+
+class Commands(BaseModel):
+    ready: Optional[str] = None
+    """
+    ready is an optional command that is run repeatedly until it exits with a zero
+    exit code. If set, the service will first go into a Starting phase, and then
+    into a Running phase once the ready command exits with a zero exit code.
+    """
+
+    start: Optional[str] = None
+    """
+    start is the command to start and run the service. If start exits, the service
+    will transition to the following phase:
+
+    - Stopped: if the exit code is 0
+    - Failed: if the exit code is not 0 If the stop command is not set, the start
+      command will receive a SIGTERM signal when the service is requested to stop.
+      If it does not exit within 2 minutes, it will receive a SIGKILL signal.
+    """
+
+    stop: Optional[str] = None
+    """
+    stop is an optional command that runs when the service is requested to stop. If
+    set, instead of sending a SIGTERM signal to the start command, the stop command
+    will be run. Once the stop command exits, the start command will receive a
+    SIGKILL signal. If the stop command exits with a non-zero exit code, the service
+    will transition to the Failed phase. If the stop command does not exit within 2
+    minutes, a SIGKILL signal will be sent to both the start and stop commands.
+    """
+
+
+class ServiceSpec(BaseModel):
+    commands: Optional[Commands] = None
+    """
+    commands contains the commands to start, stop and check the readiness of the
+    service
+    """
+
+    desired_phase: Optional[ServicePhase] = FieldInfo(alias="desiredPhase", default=None)
+    """desired_phase is the phase the service should be in.
+
+    Used to start or stop the service.
+    """
+
+    runs_on: Optional[RunsOn] = FieldInfo(alias="runsOn", default=None)
+    """runs_on specifies the environment the service should run on."""
+
+    session: Optional[str] = None
+    """session should be changed to trigger a restart of the service.
+
+    If a service exits it will not be restarted until the session is changed.
+    """
+
+    spec_version: Optional[str] = FieldInfo(alias="specVersion", default=None)
+    """version of the spec.
+
+    The value of this field has no semantic meaning (e.g. don't interpret it as as a
+    timestamp), but it can be used to impose a partial order. If a.spec_version <
+    b.spec_version then a was the spec before b.
+    """
diff --git a/src/gitpod/types/environments/automations/service_spec_param.py b/src/gitpod/types/environments/automations/service_spec_param.py
new file mode 100644
index 0000000..469be0e
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_spec_param.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from .service_phase import ServicePhase
+from ...shared_params.runs_on import RunsOn
+
+__all__ = ["ServiceSpecParam", "Commands"]
+
+
+class Commands(TypedDict, total=False):
+    ready: str
+    """
+    ready is an optional command that is run repeatedly until it exits with a zero
+    exit code. If set, the service will first go into a Starting phase, and then
+    into a Running phase once the ready command exits with a zero exit code.
+    """
+
+    start: str
+    """
+    start is the command to start and run the service. If start exits, the service
+    will transition to the following phase:
+
+    - Stopped: if the exit code is 0
+    - Failed: if the exit code is not 0 If the stop command is not set, the start
+      command will receive a SIGTERM signal when the service is requested to stop.
+      If it does not exit within 2 minutes, it will receive a SIGKILL signal.
+    """
+
+    stop: str
+    """
+    stop is an optional command that runs when the service is requested to stop. If
+    set, instead of sending a SIGTERM signal to the start command, the stop command
+    will be run. Once the stop command exits, the start command will receive a
+    SIGKILL signal. If the stop command exits with a non-zero exit code, the service
+    will transition to the Failed phase. If the stop command does not exit within 2
+    minutes, a SIGKILL signal will be sent to both the start and stop commands.
+    """
+
+
+class ServiceSpecParam(TypedDict, total=False):
+    commands: Commands
+    """
+    commands contains the commands to start, stop and check the readiness of the
+    service
+    """
+
+    desired_phase: Annotated[ServicePhase, PropertyInfo(alias="desiredPhase")]
+    """desired_phase is the phase the service should be in.
+
+    Used to start or stop the service.
+    """
+
+    runs_on: Annotated[RunsOn, PropertyInfo(alias="runsOn")]
+    """runs_on specifies the environment the service should run on."""
+
+    session: str
+    """session should be changed to trigger a restart of the service.
+
+    If a service exits it will not be restarted until the session is changed.
+    """
+
+    spec_version: Annotated[str, PropertyInfo(alias="specVersion")]
+    """version of the spec.
+
+    The value of this field has no semantic meaning (e.g. don't interpret it as as a
+    timestamp), but it can be used to impose a partial order. If a.spec_version <
+    b.spec_version then a was the spec before b.
+    """
diff --git a/src/gitpod/types/environments/automations/service_start_params.py b/src/gitpod/types/environments/automations/service_start_params.py
new file mode 100644
index 0000000..237ab1e
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_start_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ServiceStartParams"]
+
+
+class ServiceStartParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations/service_status.py b/src/gitpod/types/environments/automations/service_status.py
new file mode 100644
index 0000000..b95049b
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_status.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from .service_phase import ServicePhase
+
+__all__ = ["ServiceStatus"]
+
+
+class ServiceStatus(BaseModel):
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message summarises why the service failed to operate.
+
+    If this is non-empty the service has failed to operate and will likely
+    transition to a failed state.
+    """
+
+    log_url: Optional[str] = FieldInfo(alias="logUrl", default=None)
+    """log_url contains the URL at which the service logs can be accessed."""
+
+    output: Optional[Dict[str, str]] = None
+    """
+    output contains the output of the service. setting an output field to empty
+    string will unset it.
+    """
+
+    phase: Optional[ServicePhase] = None
+    """phase is the current phase of the service."""
+
+    session: Optional[str] = None
+    """session is the current session of the service."""
+
+    status_version: Optional[str] = FieldInfo(alias="statusVersion", default=None)
+    """version of the status update.
+
+    Service instances themselves are unversioned, but their status has different
+    versions. The value of this field has no semantic meaning (e.g. don't interpret
+    it as as a timestamp), but it can be used to impose a partial order. If
+    a.status_version < b.status_version then a was the status before b.
+    """
diff --git a/src/gitpod/types/environments/automations/service_stop_params.py b/src/gitpod/types/environments/automations/service_stop_params.py
new file mode 100644
index 0000000..fcb5e25
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_stop_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ServiceStopParams"]
+
+
+class ServiceStopParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations/service_update_params.py b/src/gitpod/types/environments/automations/service_update_params.py
new file mode 100644
index 0000000..3fba2a0
--- /dev/null
+++ b/src/gitpod/types/environments/automations/service_update_params.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Iterable, Optional
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from .service_phase import ServicePhase
+from ...shared_params.runs_on import RunsOn
+from ...shared_params.automation_trigger import AutomationTrigger
+
+__all__ = ["ServiceUpdateParams", "Metadata", "MetadataTriggeredBy", "Spec", "SpecCommands", "Status"]
+
+
+class ServiceUpdateParams(TypedDict, total=False):
+    id: str
+
+    metadata: Metadata
+
+    spec: Spec
+    """Changing the spec of a service is a complex operation.
+
+    The spec of a service can only be updated if the service is in a stopped state.
+    If the service is running, it must be stopped first.
+    """
+
+    status: Status
+    """Service status updates are only expected from the executing environment.
+
+    As a client of this API you are not expected to provide this field. Updating
+    this field requires the `environmentservice:update_status` permission.
+    """
+
+
+class MetadataTriggeredBy(TypedDict, total=False):
+    trigger: Iterable[AutomationTrigger]
+
+
+class Metadata(TypedDict, total=False):
+    description: Optional[str]
+
+    name: Optional[str]
+
+    triggered_by: Annotated[Optional[MetadataTriggeredBy], PropertyInfo(alias="triggeredBy")]
+
+
+class SpecCommands(TypedDict, total=False):
+    ready: Optional[str]
+
+    start: Optional[str]
+
+    stop: Optional[str]
+
+
+class Spec(TypedDict, total=False):
+    commands: Optional[SpecCommands]
+
+    runs_on: Annotated[Optional[RunsOn], PropertyInfo(alias="runsOn")]
+
+
+class Status(TypedDict, total=False):
+    failure_message: Annotated[Optional[str], PropertyInfo(alias="failureMessage")]
+
+    log_url: Annotated[Optional[str], PropertyInfo(alias="logUrl")]
+
+    output: Dict[str, str]
+    """setting an output field to empty string will unset it."""
+
+    phase: Optional[ServicePhase]
+
+    session: Optional[str]
diff --git a/src/gitpod/types/environments/automations/task_create_params.py b/src/gitpod/types/environments/automations/task_create_params.py
new file mode 100644
index 0000000..5bb8916
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_create_params.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from ...shared_params.task_spec import TaskSpec
+from ...shared_params.task_metadata import TaskMetadata
+
+__all__ = ["TaskCreateParams"]
+
+
+class TaskCreateParams(TypedDict, total=False):
+    depends_on: Annotated[List[str], PropertyInfo(alias="dependsOn")]
+
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+
+    metadata: TaskMetadata
+
+    spec: TaskSpec
diff --git a/src/gitpod/types/environments/automations/task_create_response.py b/src/gitpod/types/environments/automations/task_create_response.py
new file mode 100644
index 0000000..a00af2b
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_create_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ...._models import BaseModel
+from ...shared.task import Task
+
+__all__ = ["TaskCreateResponse"]
+
+
+class TaskCreateResponse(BaseModel):
+    task: Task
diff --git a/src/gitpod/types/environments/automations/task_delete_params.py b/src/gitpod/types/environments/automations/task_delete_params.py
new file mode 100644
index 0000000..d1e4073
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_delete_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["TaskDeleteParams"]
+
+
+class TaskDeleteParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations/task_list_params.py b/src/gitpod/types/environments/automations/task_list_params.py
new file mode 100644
index 0000000..7afc694
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_list_params.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["TaskListParams", "Filter", "Pagination"]
+
+
+class TaskListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+    """filter contains the filter options for listing tasks"""
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing tasks"""
+
+
+class Filter(TypedDict, total=False):
+    environment_ids: Annotated[List[str], PropertyInfo(alias="environmentIds")]
+    """environment_ids filters the response to only tasks of these environments"""
+
+    references: List[str]
+    """references filters the response to only services with these references"""
+
+    task_ids: Annotated[List[str], PropertyInfo(alias="taskIds")]
+    """task_ids filters the response to only tasks with these IDs"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/environments/automations/task_retrieve_params.py b/src/gitpod/types/environments/automations/task_retrieve_params.py
new file mode 100644
index 0000000..acd7ec7
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_retrieve_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["TaskRetrieveParams"]
+
+
+class TaskRetrieveParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations/task_retrieve_response.py b/src/gitpod/types/environments/automations/task_retrieve_response.py
new file mode 100644
index 0000000..546b7b0
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_retrieve_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ...._models import BaseModel
+from ...shared.task import Task
+
+__all__ = ["TaskRetrieveResponse"]
+
+
+class TaskRetrieveResponse(BaseModel):
+    task: Task
diff --git a/src/gitpod/types/environments/automations/task_start_params.py b/src/gitpod/types/environments/automations/task_start_params.py
new file mode 100644
index 0000000..e9b4703
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_start_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["TaskStartParams"]
+
+
+class TaskStartParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations/task_start_response.py b/src/gitpod/types/environments/automations/task_start_response.py
new file mode 100644
index 0000000..bb0fb10
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_start_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from ...shared.task_execution import TaskExecution
+
+__all__ = ["TaskStartResponse"]
+
+
+class TaskStartResponse(BaseModel):
+    task_execution: TaskExecution = FieldInfo(alias="taskExecution")
diff --git a/src/gitpod/types/environments/automations/task_update_params.py b/src/gitpod/types/environments/automations/task_update_params.py
new file mode 100644
index 0000000..4b0d59a
--- /dev/null
+++ b/src/gitpod/types/environments/automations/task_update_params.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Iterable, Optional
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from ...shared_params.runs_on import RunsOn
+from ...shared_params.automation_trigger import AutomationTrigger
+
+__all__ = ["TaskUpdateParams", "Metadata", "MetadataTriggeredBy", "Spec"]
+
+
+class TaskUpdateParams(TypedDict, total=False):
+    id: str
+
+    depends_on: Annotated[List[str], PropertyInfo(alias="dependsOn")]
+    """dependencies specifies the IDs of the automations this task depends on."""
+
+    metadata: Metadata
+
+    spec: Spec
+
+
+class MetadataTriggeredBy(TypedDict, total=False):
+    trigger: Iterable[AutomationTrigger]
+
+
+class Metadata(TypedDict, total=False):
+    description: Optional[str]
+
+    name: Optional[str]
+
+    triggered_by: Annotated[Optional[MetadataTriggeredBy], PropertyInfo(alias="triggeredBy")]
+
+
+class Spec(TypedDict, total=False):
+    command: Optional[str]
+
+    runs_on: Annotated[Optional[RunsOn], PropertyInfo(alias="runsOn")]
diff --git a/src/gitpod/types/environments/automations/tasks/__init__.py b/src/gitpod/types/environments/automations/tasks/__init__.py
new file mode 100644
index 0000000..f6dd9ac
--- /dev/null
+++ b/src/gitpod/types/environments/automations/tasks/__init__.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .execution_list_params import ExecutionListParams as ExecutionListParams
+from .execution_stop_params import ExecutionStopParams as ExecutionStopParams
+from .execution_retrieve_params import ExecutionRetrieveParams as ExecutionRetrieveParams
+from .execution_retrieve_response import ExecutionRetrieveResponse as ExecutionRetrieveResponse
diff --git a/src/gitpod/types/environments/automations/tasks/execution_list_params.py b/src/gitpod/types/environments/automations/tasks/execution_list_params.py
new file mode 100644
index 0000000..0441fb5
--- /dev/null
+++ b/src/gitpod/types/environments/automations/tasks/execution_list_params.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from ....._utils import PropertyInfo
+from ....shared.task_execution_phase import TaskExecutionPhase
+
+__all__ = ["ExecutionListParams", "Filter", "Pagination"]
+
+
+class ExecutionListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+    """filter contains the filter options for listing task runs"""
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing task runs"""
+
+
+class Filter(TypedDict, total=False):
+    environment_ids: Annotated[List[str], PropertyInfo(alias="environmentIds")]
+    """environment_ids filters the response to only task runs of these environments"""
+
+    phases: List[TaskExecutionPhase]
+    """phases filters the response to only task runs in these phases"""
+
+    task_ids: Annotated[List[str], PropertyInfo(alias="taskIds")]
+    """task_ids filters the response to only task runs of these tasks"""
+
+    task_references: Annotated[List[str], PropertyInfo(alias="taskReferences")]
+    """task_references filters the response to only task runs with this reference"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/environments/automations/tasks/execution_retrieve_params.py b/src/gitpod/types/environments/automations/tasks/execution_retrieve_params.py
new file mode 100644
index 0000000..85fa9fc
--- /dev/null
+++ b/src/gitpod/types/environments/automations/tasks/execution_retrieve_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ExecutionRetrieveParams"]
+
+
+class ExecutionRetrieveParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations/tasks/execution_retrieve_response.py b/src/gitpod/types/environments/automations/tasks/execution_retrieve_response.py
new file mode 100644
index 0000000..59483ce
--- /dev/null
+++ b/src/gitpod/types/environments/automations/tasks/execution_retrieve_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+from ....shared.task_execution import TaskExecution
+
+__all__ = ["ExecutionRetrieveResponse"]
+
+
+class ExecutionRetrieveResponse(BaseModel):
+    task_execution: TaskExecution = FieldInfo(alias="taskExecution")
diff --git a/src/gitpod/types/environments/automations/tasks/execution_stop_params.py b/src/gitpod/types/environments/automations/tasks/execution_stop_params.py
new file mode 100644
index 0000000..9a5188e
--- /dev/null
+++ b/src/gitpod/types/environments/automations/tasks/execution_stop_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ExecutionStopParams"]
+
+
+class ExecutionStopParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/environments/automations_file_param.py b/src/gitpod/types/environments/automations_file_param.py
new file mode 100644
index 0000000..e2c0a7e
--- /dev/null
+++ b/src/gitpod/types/environments/automations_file_param.py
@@ -0,0 +1,77 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, List
+from typing_extensions import Literal, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared_params.runs_on import RunsOn
+
+__all__ = ["AutomationsFileParam", "Services", "ServicesCommands", "Tasks"]
+
+
+class ServicesCommands(TypedDict, total=False):
+    ready: str
+    """
+    ready is an optional command that is run repeatedly until it exits with a zero
+    exit code. If set, the service will first go into a Starting phase, and then
+    into a Running phase once the ready command exits with a zero exit code.
+    """
+
+    start: str
+    """
+    start is the command to start and run the service. If start exits, the service
+    will transition to the following phase:
+
+    - Stopped: if the exit code is 0
+    - Failed: if the exit code is not 0 If the stop command is not set, the start
+      command will receive a SIGTERM signal when the service is requested to stop.
+      If it does not exit within 2 minutes, it will receive a SIGKILL signal.
+    """
+
+    stop: str
+    """
+    stop is an optional command that runs when the service is requested to stop. If
+    set, instead of sending a SIGTERM signal to the start command, the stop command
+    will be run. Once the stop command exits, the start command will receive a
+    SIGKILL signal. If the stop command exits with a non-zero exit code, the service
+    will transition to the Failed phase. If the stop command does not exit within 2
+    minutes, a SIGKILL signal will be sent to both the start and stop commands.
+    """
+
+
+class Services(TypedDict, total=False):
+    commands: ServicesCommands
+
+    description: str
+
+    name: str
+
+    runs_on: Annotated[RunsOn, PropertyInfo(alias="runsOn")]
+
+    triggered_by: Annotated[
+        List[Literal["manual", "postEnvironmentStart", "postDevcontainerStart"]], PropertyInfo(alias="triggeredBy")
+    ]
+
+
+class Tasks(TypedDict, total=False):
+    command: str
+
+    depends_on: Annotated[List[str], PropertyInfo(alias="dependsOn")]
+
+    description: str
+
+    name: str
+
+    runs_on: Annotated[RunsOn, PropertyInfo(alias="runsOn")]
+
+    triggered_by: Annotated[
+        List[Literal["manual", "postEnvironmentStart", "postDevcontainerStart"]], PropertyInfo(alias="triggeredBy")
+    ]
+
+
+class AutomationsFileParam(TypedDict, total=False):
+    services: Dict[str, Services]
+
+    tasks: Dict[str, Tasks]
diff --git a/src/gitpod/types/environments/class_list_params.py b/src/gitpod/types/environments/class_list_params.py
new file mode 100644
index 0000000..1abb82c
--- /dev/null
+++ b/src/gitpod/types/environments/class_list_params.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from ..runner_kind import RunnerKind
+from ..runner_provider import RunnerProvider
+
+__all__ = ["ClassListParams", "Filter", "Pagination"]
+
+
+class ClassListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing environment classes"""
+
+
+class Filter(TypedDict, total=False):
+    can_create_environments: Annotated[Optional[bool], PropertyInfo(alias="canCreateEnvironments")]
+    """
+    can_create_environments filters the response to only environment classes that
+    can be used to create new environments by the caller. Unlike enabled, which
+    indicates general availability, this ensures the caller only sees environment
+    classes they are allowed to use.
+    """
+
+    enabled: Optional[bool]
+    """
+    enabled filters the response to only enabled or disabled environment classes. If
+    not set, all environment classes are returned.
+    """
+
+    runner_ids: Annotated[List[str], PropertyInfo(alias="runnerIds")]
+    """runner_ids filters the response to only EnvironmentClasses of these Runner IDs"""
+
+    runner_kinds: Annotated[List[RunnerKind], PropertyInfo(alias="runnerKinds")]
+    """
+    runner_kind filters the response to only environment classes from runners of
+    these kinds.
+    """
+
+    runner_providers: Annotated[List[RunnerProvider], PropertyInfo(alias="runnerProviders")]
+    """
+    runner_providers filters the response to only environment classes from runners
+    of these providers.
+    """
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/event_list_params.py b/src/gitpod/types/event_list_params.py
new file mode 100644
index 0000000..f444df6
--- /dev/null
+++ b/src/gitpod/types/event_list_params.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .resource_type import ResourceType
+from .shared.principal import Principal
+
+__all__ = ["EventListParams", "Filter", "Pagination"]
+
+
+class EventListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing environments"""
+
+
+class Filter(TypedDict, total=False):
+    actor_ids: Annotated[List[str], PropertyInfo(alias="actorIds")]
+
+    actor_principals: Annotated[List[Principal], PropertyInfo(alias="actorPrincipals")]
+
+    subject_ids: Annotated[List[str], PropertyInfo(alias="subjectIds")]
+
+    subject_types: Annotated[List[ResourceType], PropertyInfo(alias="subjectTypes")]
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/event_list_response.py b/src/gitpod/types/event_list_response.py
new file mode 100644
index 0000000..ce8fb8b
--- /dev/null
+++ b/src/gitpod/types/event_list_response.py
@@ -0,0 +1,118 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .resource_type import ResourceType
+from .shared.principal import Principal
+
+__all__ = ["EventListResponse"]
+
+
+class EventListResponse(BaseModel):
+    id: Optional[str] = None
+
+    action: Optional[str] = None
+
+    actor_id: Optional[str] = FieldInfo(alias="actorId", default=None)
+
+    actor_principal: Optional[Principal] = FieldInfo(alias="actorPrincipal", default=None)
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    subject_id: Optional[str] = FieldInfo(alias="subjectId", default=None)
+
+    subject_type: Optional[ResourceType] = FieldInfo(alias="subjectType", default=None)
diff --git a/src/gitpod/types/event_watch_params.py b/src/gitpod/types/event_watch_params.py
new file mode 100644
index 0000000..0465a09
--- /dev/null
+++ b/src/gitpod/types/event_watch_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["EventWatchParams"]
+
+
+class EventWatchParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """
+    Environment scope produces events for the environment itself, all tasks, task
+    executions, and services associated with that environment.
+    """
+
+    organization: bool
+    """
+    Organization scope produces events for all projects, runners and environments
+    the caller can see within their organization. No task, task execution or service
+    events are produed.
+    """
diff --git a/src/gitpod/types/event_watch_response.py b/src/gitpod/types/event_watch_response.py
new file mode 100644
index 0000000..9780ee1
--- /dev/null
+++ b/src/gitpod/types/event_watch_response.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .resource_type import ResourceType
+from .resource_operation import ResourceOperation
+
+__all__ = ["EventWatchResponse"]
+
+
+class EventWatchResponse(BaseModel):
+    operation: Optional[ResourceOperation] = None
+
+    resource_id: Optional[str] = FieldInfo(alias="resourceId", default=None)
+
+    resource_type: Optional[ResourceType] = FieldInfo(alias="resourceType", default=None)
diff --git a/src/gitpod/types/gateway_info.py b/src/gitpod/types/gateway_info.py
new file mode 100644
index 0000000..d8211a9
--- /dev/null
+++ b/src/gitpod/types/gateway_info.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .shared.gateway import Gateway
+
+__all__ = ["GatewayInfo"]
+
+
+class GatewayInfo(BaseModel):
+    gateway: Optional[Gateway] = None
+    """Gateway represents a system gateway that provides access to services"""
+
+    latency: Optional[str] = None
+    """latency is the round-trip time of the runner to the gateway in milliseconds."""
diff --git a/src/gitpod/types/gateway_list_params.py b/src/gitpod/types/gateway_list_params.py
new file mode 100644
index 0000000..8afa467
--- /dev/null
+++ b/src/gitpod/types/gateway_list_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["GatewayListParams", "Pagination"]
+
+
+class GatewayListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing gateways"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/group.py b/src/gitpod/types/group.py
new file mode 100644
index 0000000..a98b80b
--- /dev/null
+++ b/src/gitpod/types/group.py
@@ -0,0 +1,205 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["Group"]
+
+
+class Group(BaseModel):
+    id: Optional[str] = None
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    name: Optional[str] = None
+
+    organization_id: Optional[str] = FieldInfo(alias="organizationId", default=None)
+
+    system_managed: Optional[bool] = FieldInfo(alias="systemManaged", default=None)
+    """system_managed indicates that this group is created by the system automatically"""
+
+    updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
diff --git a/src/gitpod/types/group_list_params.py b/src/gitpod/types/group_list_params.py
new file mode 100644
index 0000000..9b7e7e8
--- /dev/null
+++ b/src/gitpod/types/group_list_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["GroupListParams", "Pagination"]
+
+
+class GroupListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing groups"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/id_token_version.py b/src/gitpod/types/id_token_version.py
new file mode 100644
index 0000000..ecbfdba
--- /dev/null
+++ b/src/gitpod/types/id_token_version.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["IDTokenVersion"]
+
+IDTokenVersion: TypeAlias = Literal["ID_TOKEN_VERSION_UNSPECIFIED", "ID_TOKEN_VERSION_V1", "ID_TOKEN_VERSION_V2"]
diff --git a/src/gitpod/types/identity_exchange_token_params.py b/src/gitpod/types/identity_exchange_token_params.py
new file mode 100644
index 0000000..e9114bd
--- /dev/null
+++ b/src/gitpod/types/identity_exchange_token_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["IdentityExchangeTokenParams"]
+
+
+class IdentityExchangeTokenParams(TypedDict, total=False):
+    exchange_token: Annotated[str, PropertyInfo(alias="exchangeToken")]
+    """exchange_token is the token to exchange"""
diff --git a/src/gitpod/types/identity_exchange_token_response.py b/src/gitpod/types/identity_exchange_token_response.py
new file mode 100644
index 0000000..c101adb
--- /dev/null
+++ b/src/gitpod/types/identity_exchange_token_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["IdentityExchangeTokenResponse"]
+
+
+class IdentityExchangeTokenResponse(BaseModel):
+    access_token: Optional[str] = FieldInfo(alias="accessToken", default=None)
+    """access_token is the new access token"""
diff --git a/src/gitpod/types/identity_get_authenticated_identity_params.py b/src/gitpod/types/identity_get_authenticated_identity_params.py
new file mode 100644
index 0000000..2ed3b1f
--- /dev/null
+++ b/src/gitpod/types/identity_get_authenticated_identity_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["IdentityGetAuthenticatedIdentityParams"]
+
+
+class IdentityGetAuthenticatedIdentityParams(TypedDict, total=False):
+    empty: bool
diff --git a/src/gitpod/types/identity_get_authenticated_identity_response.py b/src/gitpod/types/identity_get_authenticated_identity_response.py
new file mode 100644
index 0000000..f04ad09
--- /dev/null
+++ b/src/gitpod/types/identity_get_authenticated_identity_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.subject import Subject
+
+__all__ = ["IdentityGetAuthenticatedIdentityResponse"]
+
+
+class IdentityGetAuthenticatedIdentityResponse(BaseModel):
+    organization_id: Optional[str] = FieldInfo(alias="organizationId", default=None)
+
+    subject: Optional[Subject] = None
+    """subject is the identity of the current user"""
diff --git a/src/gitpod/types/identity_get_id_token_params.py b/src/gitpod/types/identity_get_id_token_params.py
new file mode 100644
index 0000000..c5686ae
--- /dev/null
+++ b/src/gitpod/types/identity_get_id_token_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import TypedDict
+
+from .id_token_version import IDTokenVersion
+
+__all__ = ["IdentityGetIDTokenParams"]
+
+
+class IdentityGetIDTokenParams(TypedDict, total=False):
+    audience: List[str]
+
+    version: IDTokenVersion
+    """version is the version of the ID token."""
diff --git a/src/gitpod/types/identity_get_id_token_response.py b/src/gitpod/types/identity_get_id_token_response.py
new file mode 100644
index 0000000..e327e68
--- /dev/null
+++ b/src/gitpod/types/identity_get_id_token_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["IdentityGetIDTokenResponse"]
+
+
+class IdentityGetIDTokenResponse(BaseModel):
+    token: Optional[str] = None
diff --git a/src/gitpod/types/invite_domains.py b/src/gitpod/types/invite_domains.py
new file mode 100644
index 0000000..32a0c7f
--- /dev/null
+++ b/src/gitpod/types/invite_domains.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .._models import BaseModel
+
+__all__ = ["InviteDomains"]
+
+
+class InviteDomains(BaseModel):
+    domains: Optional[List[str]] = None
+    """domains is the list of domains that are allowed to join the organization"""
diff --git a/src/gitpod/types/invite_domains_param.py b/src/gitpod/types/invite_domains_param.py
new file mode 100644
index 0000000..d38601a
--- /dev/null
+++ b/src/gitpod/types/invite_domains_param.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import TypedDict
+
+__all__ = ["InviteDomainsParam"]
+
+
+class InviteDomainsParam(TypedDict, total=False):
+    domains: List[str]
+    """domains is the list of domains that are allowed to join the organization"""
diff --git a/src/gitpod/types/joinable_organization.py b/src/gitpod/types/joinable_organization.py
new file mode 100644
index 0000000..a5b6cb6
--- /dev/null
+++ b/src/gitpod/types/joinable_organization.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["JoinableOrganization"]
+
+
+class JoinableOrganization(BaseModel):
+    organization_id: str = FieldInfo(alias="organizationId")
+    """organization_id is the id of the organization the user can join"""
+
+    organization_name: str = FieldInfo(alias="organizationName")
+    """organization_name is the name of the organization the user can join"""
+
+    organization_member_count: Optional[int] = FieldInfo(alias="organizationMemberCount", default=None)
+    """
+    organization_member_count is the member count of the organization the user can
+    join
+    """
diff --git a/src/gitpod/types/log_level.py b/src/gitpod/types/log_level.py
new file mode 100644
index 0000000..7f2abd1
--- /dev/null
+++ b/src/gitpod/types/log_level.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["LogLevel"]
+
+LogLevel: TypeAlias = Literal[
+    "LOG_LEVEL_UNSPECIFIED", "LOG_LEVEL_DEBUG", "LOG_LEVEL_INFO", "LOG_LEVEL_WARN", "LOG_LEVEL_ERROR"
+]
diff --git a/src/gitpod/types/login_provider.py b/src/gitpod/types/login_provider.py
new file mode 100644
index 0000000..e23ebd8
--- /dev/null
+++ b/src/gitpod/types/login_provider.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["LoginProvider"]
+
+
+class LoginProvider(BaseModel):
+    provider: str
+    """provider is the provider used by this login method, e.g.
+
+    "github", "google", "custom"
+    """
+
+    login_url: Optional[str] = FieldInfo(alias="loginUrl", default=None)
+    """
+    login_url is the URL to redirect the browser agent to for login, when provider
+    is "custom"
+    """
diff --git a/src/gitpod/types/metrics_configuration.py b/src/gitpod/types/metrics_configuration.py
new file mode 100644
index 0000000..0f6a075
--- /dev/null
+++ b/src/gitpod/types/metrics_configuration.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["MetricsConfiguration"]
+
+
+class MetricsConfiguration(BaseModel):
+    enabled: Optional[bool] = None
+    """enabled indicates whether the runner should collect metrics"""
+
+    password: Optional[str] = None
+    """password is the password to use for the metrics collector"""
+
+    url: Optional[str] = None
+    """url is the URL of the metrics collector"""
+
+    username: Optional[str] = None
+    """username is the username to use for the metrics collector"""
diff --git a/src/gitpod/types/metrics_configuration_param.py b/src/gitpod/types/metrics_configuration_param.py
new file mode 100644
index 0000000..252253e
--- /dev/null
+++ b/src/gitpod/types/metrics_configuration_param.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["MetricsConfigurationParam"]
+
+
+class MetricsConfigurationParam(TypedDict, total=False):
+    enabled: bool
+    """enabled indicates whether the runner should collect metrics"""
+
+    password: str
+    """password is the password to use for the metrics collector"""
+
+    url: str
+    """url is the URL of the metrics collector"""
+
+    username: str
+    """username is the username to use for the metrics collector"""
diff --git a/src/gitpod/types/organization.py b/src/gitpod/types/organization.py
new file mode 100644
index 0000000..9ecda14
--- /dev/null
+++ b/src/gitpod/types/organization.py
@@ -0,0 +1,207 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .invite_domains import InviteDomains
+from .organization_tier import OrganizationTier
+
+__all__ = ["Organization"]
+
+
+class Organization(BaseModel):
+    id: str
+
+    created_at: datetime = FieldInfo(alias="createdAt")
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    name: str
+
+    tier: OrganizationTier
+    """The tier of the organization - free or enterprise"""
+
+    updated_at: datetime = FieldInfo(alias="updatedAt")
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    invite_domains: Optional[InviteDomains] = FieldInfo(alias="inviteDomains", default=None)
diff --git a/src/gitpod/types/organization_create_params.py b/src/gitpod/types/organization_create_params.py
new file mode 100644
index 0000000..8251342
--- /dev/null
+++ b/src/gitpod/types/organization_create_params.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["OrganizationCreateParams"]
+
+
+class OrganizationCreateParams(TypedDict, total=False):
+    name: Required[str]
+    """name is the organization name"""
+
+    invite_accounts_with_matching_domain: Annotated[bool, PropertyInfo(alias="inviteAccountsWithMatchingDomain")]
+    """
+    Should other Accounts with the same domain be automatically invited to the
+    organization?
+    """
+
+    join_organization: Annotated[bool, PropertyInfo(alias="joinOrganization")]
+    """
+    join_organization decides whether the Identity issuing this request joins the
+    org on creation
+    """
diff --git a/src/gitpod/types/organization_create_response.py b/src/gitpod/types/organization_create_response.py
new file mode 100644
index 0000000..51cb8f7
--- /dev/null
+++ b/src/gitpod/types/organization_create_response.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .organization import Organization
+from .organization_member import OrganizationMember
+
+__all__ = ["OrganizationCreateResponse"]
+
+
+class OrganizationCreateResponse(BaseModel):
+    organization: Organization
+    """organization is the created organization"""
+
+    member: Optional[OrganizationMember] = None
+    """member is the member that joined the org on creation.
+
+    Only set if specified "join_organization" is "true" in the request.
+    """
diff --git a/src/gitpod/types/organization_delete_params.py b/src/gitpod/types/organization_delete_params.py
new file mode 100644
index 0000000..8402335
--- /dev/null
+++ b/src/gitpod/types/organization_delete_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["OrganizationDeleteParams"]
+
+
+class OrganizationDeleteParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organization_id is the ID of the organization to delete"""
diff --git a/src/gitpod/types/organization_join_params.py b/src/gitpod/types/organization_join_params.py
new file mode 100644
index 0000000..25f8223
--- /dev/null
+++ b/src/gitpod/types/organization_join_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["OrganizationJoinParams"]
+
+
+class OrganizationJoinParams(TypedDict, total=False):
+    invite_id: Annotated[str, PropertyInfo(alias="inviteId")]
+    """invite_id is the unique identifier of the invite to join the organization."""
+
+    organization_id: Annotated[str, PropertyInfo(alias="organizationId")]
+    """organization_id is the unique identifier of the Organization to join."""
diff --git a/src/gitpod/types/organization_join_response.py b/src/gitpod/types/organization_join_response.py
new file mode 100644
index 0000000..78e8ff9
--- /dev/null
+++ b/src/gitpod/types/organization_join_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .organization_member import OrganizationMember
+
+__all__ = ["OrganizationJoinResponse"]
+
+
+class OrganizationJoinResponse(BaseModel):
+    member: OrganizationMember
+    """member is the member that was created by joining the organization."""
diff --git a/src/gitpod/types/organization_leave_params.py b/src/gitpod/types/organization_leave_params.py
new file mode 100644
index 0000000..f875644
--- /dev/null
+++ b/src/gitpod/types/organization_leave_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["OrganizationLeaveParams"]
+
+
+class OrganizationLeaveParams(TypedDict, total=False):
+    user_id: Required[Annotated[str, PropertyInfo(alias="userId")]]
diff --git a/src/gitpod/types/organization_list_members_params.py b/src/gitpod/types/organization_list_members_params.py
new file mode 100644
index 0000000..aa85f58
--- /dev/null
+++ b/src/gitpod/types/organization_list_members_params.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["OrganizationListMembersParams", "Pagination"]
+
+
+class OrganizationListMembersParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organization_id is the ID of the organization to list members for"""
+
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing members"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/organization_member.py b/src/gitpod/types/organization_member.py
new file mode 100644
index 0000000..6745f0b
--- /dev/null
+++ b/src/gitpod/types/organization_member.py
@@ -0,0 +1,121 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.user_status import UserStatus
+from .shared.organization_role import OrganizationRole
+
+__all__ = ["OrganizationMember"]
+
+
+class OrganizationMember(BaseModel):
+    email: str
+
+    full_name: str = FieldInfo(alias="fullName")
+
+    login_provider: str = FieldInfo(alias="loginProvider")
+    """login_provider is the login provider the user uses to sign in"""
+
+    member_since: datetime = FieldInfo(alias="memberSince")
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    role: OrganizationRole
+
+    status: UserStatus
+
+    user_id: str = FieldInfo(alias="userId")
+
+    avatar_url: Optional[str] = FieldInfo(alias="avatarUrl", default=None)
diff --git a/src/gitpod/types/organization_retrieve_params.py b/src/gitpod/types/organization_retrieve_params.py
new file mode 100644
index 0000000..e81f1f3
--- /dev/null
+++ b/src/gitpod/types/organization_retrieve_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["OrganizationRetrieveParams"]
+
+
+class OrganizationRetrieveParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organization_id is the unique identifier of the Organization to retreive."""
diff --git a/src/gitpod/types/organization_retrieve_response.py b/src/gitpod/types/organization_retrieve_response.py
new file mode 100644
index 0000000..b557f52
--- /dev/null
+++ b/src/gitpod/types/organization_retrieve_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .organization import Organization
+
+__all__ = ["OrganizationRetrieveResponse"]
+
+
+class OrganizationRetrieveResponse(BaseModel):
+    organization: Organization
+    """organization is the requested organization"""
diff --git a/src/gitpod/types/organization_set_role_params.py b/src/gitpod/types/organization_set_role_params.py
new file mode 100644
index 0000000..9c9d4b8
--- /dev/null
+++ b/src/gitpod/types/organization_set_role_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.organization_role import OrganizationRole
+
+__all__ = ["OrganizationSetRoleParams"]
+
+
+class OrganizationSetRoleParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+
+    user_id: Required[Annotated[str, PropertyInfo(alias="userId")]]
+
+    role: OrganizationRole
diff --git a/src/gitpod/types/organization_tier.py b/src/gitpod/types/organization_tier.py
new file mode 100644
index 0000000..ea000b2
--- /dev/null
+++ b/src/gitpod/types/organization_tier.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["OrganizationTier"]
+
+OrganizationTier: TypeAlias = Literal[
+    "ORGANIZATION_TIER_UNSPECIFIED", "ORGANIZATION_TIER_FREE", "ORGANIZATION_TIER_ENTERPRISE"
+]
diff --git a/src/gitpod/types/organization_update_params.py b/src/gitpod/types/organization_update_params.py
new file mode 100644
index 0000000..b6018bf
--- /dev/null
+++ b/src/gitpod/types/organization_update_params.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .invite_domains_param import InviteDomainsParam
+
+__all__ = ["OrganizationUpdateParams"]
+
+
+class OrganizationUpdateParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organization_id is the ID of the organization to update the settings for."""
+
+    invite_domains: Annotated[Optional[InviteDomainsParam], PropertyInfo(alias="inviteDomains")]
+    """invite_domains is the domain allowlist of the organization"""
+
+    name: Optional[str]
+    """name is the new name of the organization"""
diff --git a/src/gitpod/types/organization_update_response.py b/src/gitpod/types/organization_update_response.py
new file mode 100644
index 0000000..d904634
--- /dev/null
+++ b/src/gitpod/types/organization_update_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .organization import Organization
+
+__all__ = ["OrganizationUpdateResponse"]
+
+
+class OrganizationUpdateResponse(BaseModel):
+    organization: Organization
+    """organization is the updated organization"""
diff --git a/src/gitpod/types/organizations/__init__.py b/src/gitpod/types/organizations/__init__.py
new file mode 100644
index 0000000..edd5451
--- /dev/null
+++ b/src/gitpod/types/organizations/__init__.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .provider_type import ProviderType as ProviderType
+from .sso_configuration import SSOConfiguration as SSOConfiguration
+from .domain_verification import DomainVerification as DomainVerification
+from .organization_invite import OrganizationInvite as OrganizationInvite
+from .invite_create_params import InviteCreateParams as InviteCreateParams
+from .policy_update_params import PolicyUpdateParams as PolicyUpdateParams
+from .organization_policies import OrganizationPolicies as OrganizationPolicies
+from .invite_create_response import InviteCreateResponse as InviteCreateResponse
+from .invite_retrieve_params import InviteRetrieveParams as InviteRetrieveParams
+from .policy_retrieve_params import PolicyRetrieveParams as PolicyRetrieveParams
+from .sso_configuration_state import SSOConfigurationState as SSOConfigurationState
+from .invite_retrieve_response import InviteRetrieveResponse as InviteRetrieveResponse
+from .policy_retrieve_response import PolicyRetrieveResponse as PolicyRetrieveResponse
+from .domain_verification_state import DomainVerificationState as DomainVerificationState
+from .invite_get_summary_params import InviteGetSummaryParams as InviteGetSummaryParams
+from .invite_get_summary_response import InviteGetSummaryResponse as InviteGetSummaryResponse
+from .sso_configuration_list_params import SSOConfigurationListParams as SSOConfigurationListParams
+from .domain_verification_list_params import DomainVerificationListParams as DomainVerificationListParams
+from .sso_configuration_create_params import SSOConfigurationCreateParams as SSOConfigurationCreateParams
+from .sso_configuration_delete_params import SSOConfigurationDeleteParams as SSOConfigurationDeleteParams
+from .sso_configuration_update_params import SSOConfigurationUpdateParams as SSOConfigurationUpdateParams
+from .domain_verification_create_params import DomainVerificationCreateParams as DomainVerificationCreateParams
+from .domain_verification_delete_params import DomainVerificationDeleteParams as DomainVerificationDeleteParams
+from .domain_verification_verify_params import DomainVerificationVerifyParams as DomainVerificationVerifyParams
+from .sso_configuration_create_response import SSOConfigurationCreateResponse as SSOConfigurationCreateResponse
+from .sso_configuration_retrieve_params import SSOConfigurationRetrieveParams as SSOConfigurationRetrieveParams
+from .domain_verification_create_response import DomainVerificationCreateResponse as DomainVerificationCreateResponse
+from .domain_verification_retrieve_params import DomainVerificationRetrieveParams as DomainVerificationRetrieveParams
+from .domain_verification_verify_response import DomainVerificationVerifyResponse as DomainVerificationVerifyResponse
+from .sso_configuration_retrieve_response import SSOConfigurationRetrieveResponse as SSOConfigurationRetrieveResponse
+from .domain_verification_retrieve_response import (
+    DomainVerificationRetrieveResponse as DomainVerificationRetrieveResponse,
+)
diff --git a/src/gitpod/types/organizations/domain_verification.py b/src/gitpod/types/organizations/domain_verification.py
new file mode 100644
index 0000000..3d2e32a
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification.py
@@ -0,0 +1,207 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .domain_verification_state import DomainVerificationState
+
+__all__ = ["DomainVerification"]
+
+
+class DomainVerification(BaseModel):
+    id: str
+
+    domain: str
+
+    organization_id: str = FieldInfo(alias="organizationId")
+
+    state: DomainVerificationState
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    verification_token: Optional[str] = FieldInfo(alias="verificationToken", default=None)
+
+    verified_at: Optional[datetime] = FieldInfo(alias="verifiedAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
diff --git a/src/gitpod/types/organizations/domain_verification_create_params.py b/src/gitpod/types/organizations/domain_verification_create_params.py
new file mode 100644
index 0000000..cfd882a
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_create_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["DomainVerificationCreateParams"]
+
+
+class DomainVerificationCreateParams(TypedDict, total=False):
+    domain: Required[str]
+
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
diff --git a/src/gitpod/types/organizations/domain_verification_create_response.py b/src/gitpod/types/organizations/domain_verification_create_response.py
new file mode 100644
index 0000000..3429a61
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_create_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .domain_verification import DomainVerification
+
+__all__ = ["DomainVerificationCreateResponse"]
+
+
+class DomainVerificationCreateResponse(BaseModel):
+    domain_verification: DomainVerification = FieldInfo(alias="domainVerification")
diff --git a/src/gitpod/types/organizations/domain_verification_delete_params.py b/src/gitpod/types/organizations/domain_verification_delete_params.py
new file mode 100644
index 0000000..d8f6a81
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_delete_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["DomainVerificationDeleteParams"]
+
+
+class DomainVerificationDeleteParams(TypedDict, total=False):
+    domain_verification_id: Required[Annotated[str, PropertyInfo(alias="domainVerificationId")]]
diff --git a/src/gitpod/types/organizations/domain_verification_list_params.py b/src/gitpod/types/organizations/domain_verification_list_params.py
new file mode 100644
index 0000000..26573f6
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_list_params.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["DomainVerificationListParams", "Pagination"]
+
+
+class DomainVerificationListParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    pagination: Pagination
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/organizations/domain_verification_retrieve_params.py b/src/gitpod/types/organizations/domain_verification_retrieve_params.py
new file mode 100644
index 0000000..7910e1f
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_retrieve_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["DomainVerificationRetrieveParams"]
+
+
+class DomainVerificationRetrieveParams(TypedDict, total=False):
+    domain_verification_id: Required[Annotated[str, PropertyInfo(alias="domainVerificationId")]]
diff --git a/src/gitpod/types/organizations/domain_verification_retrieve_response.py b/src/gitpod/types/organizations/domain_verification_retrieve_response.py
new file mode 100644
index 0000000..7f8d7e1
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_retrieve_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .domain_verification import DomainVerification
+
+__all__ = ["DomainVerificationRetrieveResponse"]
+
+
+class DomainVerificationRetrieveResponse(BaseModel):
+    domain_verification: DomainVerification = FieldInfo(alias="domainVerification")
diff --git a/src/gitpod/types/organizations/domain_verification_state.py b/src/gitpod/types/organizations/domain_verification_state.py
new file mode 100644
index 0000000..cc7a6d8
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_state.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["DomainVerificationState"]
+
+DomainVerificationState: TypeAlias = Literal[
+    "DOMAIN_VERIFICATION_STATE_UNSPECIFIED", "DOMAIN_VERIFICATION_STATE_PENDING", "DOMAIN_VERIFICATION_STATE_VERIFIED"
+]
diff --git a/src/gitpod/types/organizations/domain_verification_verify_params.py b/src/gitpod/types/organizations/domain_verification_verify_params.py
new file mode 100644
index 0000000..1f0c3e8
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_verify_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["DomainVerificationVerifyParams"]
+
+
+class DomainVerificationVerifyParams(TypedDict, total=False):
+    domain_verification_id: Required[Annotated[str, PropertyInfo(alias="domainVerificationId")]]
diff --git a/src/gitpod/types/organizations/domain_verification_verify_response.py b/src/gitpod/types/organizations/domain_verification_verify_response.py
new file mode 100644
index 0000000..307656e
--- /dev/null
+++ b/src/gitpod/types/organizations/domain_verification_verify_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .domain_verification import DomainVerification
+
+__all__ = ["DomainVerificationVerifyResponse"]
+
+
+class DomainVerificationVerifyResponse(BaseModel):
+    domain_verification: DomainVerification = FieldInfo(alias="domainVerification")
diff --git a/src/gitpod/types/organizations/invite_create_params.py b/src/gitpod/types/organizations/invite_create_params.py
new file mode 100644
index 0000000..32455ba
--- /dev/null
+++ b/src/gitpod/types/organizations/invite_create_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["InviteCreateParams"]
+
+
+class InviteCreateParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
diff --git a/src/gitpod/types/organizations/invite_create_response.py b/src/gitpod/types/organizations/invite_create_response.py
new file mode 100644
index 0000000..9a00dac
--- /dev/null
+++ b/src/gitpod/types/organizations/invite_create_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .organization_invite import OrganizationInvite
+
+__all__ = ["InviteCreateResponse"]
+
+
+class InviteCreateResponse(BaseModel):
+    invite: OrganizationInvite
diff --git a/src/gitpod/types/organizations/invite_get_summary_params.py b/src/gitpod/types/organizations/invite_get_summary_params.py
new file mode 100644
index 0000000..5b57656
--- /dev/null
+++ b/src/gitpod/types/organizations/invite_get_summary_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["InviteGetSummaryParams"]
+
+
+class InviteGetSummaryParams(TypedDict, total=False):
+    invite_id: Required[Annotated[str, PropertyInfo(alias="inviteId")]]
diff --git a/src/gitpod/types/organizations/invite_get_summary_response.py b/src/gitpod/types/organizations/invite_get_summary_response.py
new file mode 100644
index 0000000..786ae36
--- /dev/null
+++ b/src/gitpod/types/organizations/invite_get_summary_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["InviteGetSummaryResponse"]
+
+
+class InviteGetSummaryResponse(BaseModel):
+    organization_id: str = FieldInfo(alias="organizationId")
+
+    organization_member_count: Optional[int] = FieldInfo(alias="organizationMemberCount", default=None)
+
+    organization_name: Optional[str] = FieldInfo(alias="organizationName", default=None)
diff --git a/src/gitpod/types/organizations/invite_retrieve_params.py b/src/gitpod/types/organizations/invite_retrieve_params.py
new file mode 100644
index 0000000..a181214
--- /dev/null
+++ b/src/gitpod/types/organizations/invite_retrieve_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["InviteRetrieveParams"]
+
+
+class InviteRetrieveParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
diff --git a/src/gitpod/types/organizations/invite_retrieve_response.py b/src/gitpod/types/organizations/invite_retrieve_response.py
new file mode 100644
index 0000000..d00f343
--- /dev/null
+++ b/src/gitpod/types/organizations/invite_retrieve_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .organization_invite import OrganizationInvite
+
+__all__ = ["InviteRetrieveResponse"]
+
+
+class InviteRetrieveResponse(BaseModel):
+    invite: OrganizationInvite
diff --git a/src/gitpod/types/organizations/organization_invite.py b/src/gitpod/types/organizations/organization_invite.py
new file mode 100644
index 0000000..b33984a
--- /dev/null
+++ b/src/gitpod/types/organizations/organization_invite.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["OrganizationInvite"]
+
+
+class OrganizationInvite(BaseModel):
+    invite_id: str = FieldInfo(alias="inviteId")
+    """
+    invite_id is the unique identifier of the invite to join the organization. Use
+    JoinOrganization with this ID to join the organization.
+    """
diff --git a/src/gitpod/types/organizations/organization_policies.py b/src/gitpod/types/organizations/organization_policies.py
new file mode 100644
index 0000000..673c756
--- /dev/null
+++ b/src/gitpod/types/organizations/organization_policies.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["OrganizationPolicies"]
+
+
+class OrganizationPolicies(BaseModel):
+    allowed_editor_ids: List[str] = FieldInfo(alias="allowedEditorIds")
+    """
+    allowed_editor_ids is the list of editor IDs that are allowed to be used in the
+    organization
+    """
+
+    allow_local_runners: bool = FieldInfo(alias="allowLocalRunners")
+    """
+    allow_local_runners controls whether local runners are allowed to be used in the
+    organization
+    """
+
+    default_editor_id: str = FieldInfo(alias="defaultEditorId")
+    """
+    default_editor_id is the default editor ID to be used when a user doesn't
+    specify one
+    """
+
+    default_environment_image: str = FieldInfo(alias="defaultEnvironmentImage")
+    """
+    default_environment_image is the default container image when none is defined in
+    repo
+    """
+
+    maximum_environments_per_user: str = FieldInfo(alias="maximumEnvironmentsPerUser")
+    """
+    maximum_environments_per_user limits total environments (running or stopped) per
+    user
+    """
+
+    maximum_running_environments_per_user: str = FieldInfo(alias="maximumRunningEnvironmentsPerUser")
+    """
+    maximum_running_environments_per_user limits simultaneously running environments
+    per user
+    """
+
+    members_create_projects: bool = FieldInfo(alias="membersCreateProjects")
+    """members_create_projects controls whether members can create projects"""
+
+    members_require_projects: bool = FieldInfo(alias="membersRequireProjects")
+    """
+    members_require_projects controls whether environments can only be created from
+    projects by non-admin users
+    """
+
+    organization_id: str = FieldInfo(alias="organizationId")
+    """organization_id is the ID of the organization"""
+
+    port_sharing_disabled: bool = FieldInfo(alias="portSharingDisabled")
+    """
+    port_sharing_disabled controls whether port sharing is disabled in the
+    organization
+    """
+
+    maximum_environment_timeout: Optional[str] = FieldInfo(alias="maximumEnvironmentTimeout", default=None)
+    """
+    maximum_environment_timeout controls the maximum timeout allowed for
+    environments in seconds. 0 means no limit (never). Minimum duration is 30
+    minutes.
+    """
diff --git a/src/gitpod/types/organizations/policy_retrieve_params.py b/src/gitpod/types/organizations/policy_retrieve_params.py
new file mode 100644
index 0000000..8c54248
--- /dev/null
+++ b/src/gitpod/types/organizations/policy_retrieve_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PolicyRetrieveParams"]
+
+
+class PolicyRetrieveParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organization_id is the ID of the organization to retrieve policies for"""
diff --git a/src/gitpod/types/organizations/policy_retrieve_response.py b/src/gitpod/types/organizations/policy_retrieve_response.py
new file mode 100644
index 0000000..c54a1e7
--- /dev/null
+++ b/src/gitpod/types/organizations/policy_retrieve_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .organization_policies import OrganizationPolicies
+
+__all__ = ["PolicyRetrieveResponse"]
+
+
+class PolicyRetrieveResponse(BaseModel):
+    policies: OrganizationPolicies
diff --git a/src/gitpod/types/organizations/policy_update_params.py b/src/gitpod/types/organizations/policy_update_params.py
new file mode 100644
index 0000000..ca6b8ab
--- /dev/null
+++ b/src/gitpod/types/organizations/policy_update_params.py
@@ -0,0 +1,75 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PolicyUpdateParams"]
+
+
+class PolicyUpdateParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organization_id is the ID of the organization to update policies for"""
+
+    allowed_editor_ids: Annotated[List[str], PropertyInfo(alias="allowedEditorIds")]
+    """
+    allowed_editor_ids is the list of editor IDs that are allowed to be used in the
+    organization
+    """
+
+    allow_local_runners: Annotated[Optional[bool], PropertyInfo(alias="allowLocalRunners")]
+    """
+    allow_local_runners controls whether local runners are allowed to be used in the
+    organization
+    """
+
+    default_editor_id: Annotated[Optional[str], PropertyInfo(alias="defaultEditorId")]
+    """
+    default_editor_id is the default editor ID to be used when a user doesn't
+    specify one
+    """
+
+    default_environment_image: Annotated[Optional[str], PropertyInfo(alias="defaultEnvironmentImage")]
+    """
+    default_environment_image is the default container image when none is defined in
+    repo
+    """
+
+    maximum_environments_per_user: Annotated[Optional[str], PropertyInfo(alias="maximumEnvironmentsPerUser")]
+    """
+    maximum_environments_per_user limits total environments (running or stopped) per
+    user
+    """
+
+    maximum_environment_timeout: Annotated[Optional[str], PropertyInfo(alias="maximumEnvironmentTimeout")]
+    """
+    maximum_environment_timeout controls the maximum timeout allowed for
+    environments in seconds. 0 means no limit (never). Minimum duration is 30
+    minutes.
+    """
+
+    maximum_running_environments_per_user: Annotated[
+        Optional[str], PropertyInfo(alias="maximumRunningEnvironmentsPerUser")
+    ]
+    """
+    maximum_running_environments_per_user limits simultaneously running environments
+    per user
+    """
+
+    members_create_projects: Annotated[Optional[bool], PropertyInfo(alias="membersCreateProjects")]
+    """members_create_projects controls whether members can create projects"""
+
+    members_require_projects: Annotated[Optional[bool], PropertyInfo(alias="membersRequireProjects")]
+    """
+    members_require_projects controls whether environments can only be created from
+    projects by non-admin users
+    """
+
+    port_sharing_disabled: Annotated[Optional[bool], PropertyInfo(alias="portSharingDisabled")]
+    """
+    port_sharing_disabled controls whether port sharing is disabled in the
+    organization
+    """
diff --git a/src/gitpod/types/organizations/provider_type.py b/src/gitpod/types/organizations/provider_type.py
new file mode 100644
index 0000000..a5691eb
--- /dev/null
+++ b/src/gitpod/types/organizations/provider_type.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ProviderType"]
+
+ProviderType: TypeAlias = Literal["PROVIDER_TYPE_UNSPECIFIED", "PROVIDER_TYPE_BUILTIN", "PROVIDER_TYPE_CUSTOM"]
diff --git a/src/gitpod/types/organizations/sso_configuration.py b/src/gitpod/types/organizations/sso_configuration.py
new file mode 100644
index 0000000..4ae9912
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_type import ProviderType
+from .sso_configuration_state import SSOConfigurationState
+
+__all__ = ["SSOConfiguration"]
+
+
+class SSOConfiguration(BaseModel):
+    id: str
+    """id is the unique identifier of the SSO configuration"""
+
+    issuer_url: str = FieldInfo(alias="issuerUrl")
+    """issuer_url is the URL of the IdP issuer"""
+
+    organization_id: str = FieldInfo(alias="organizationId")
+
+    provider_type: ProviderType = FieldInfo(alias="providerType")
+    """provider_type defines the type of the SSO configuration"""
+
+    state: SSOConfigurationState
+    """state is the state of the SSO configuration"""
+
+    claims: Optional[Dict[str, str]] = None
+    """claims are key/value pairs that defines a mapping of claims issued by the IdP."""
+
+    client_id: Optional[str] = FieldInfo(alias="clientId", default=None)
+    """client_id is the client ID of the OIDC application set on the IdP"""
+
+    email_domain: Optional[str] = FieldInfo(alias="emailDomain", default=None)
diff --git a/src/gitpod/types/organizations/sso_configuration_create_params.py b/src/gitpod/types/organizations/sso_configuration_create_params.py
new file mode 100644
index 0000000..cd14f5c
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_create_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["SSOConfigurationCreateParams"]
+
+
+class SSOConfigurationCreateParams(TypedDict, total=False):
+    client_id: Required[Annotated[str, PropertyInfo(alias="clientId")]]
+    """client_id is the client ID of the OIDC application set on the IdP"""
+
+    client_secret: Required[Annotated[str, PropertyInfo(alias="clientSecret")]]
+    """client_secret is the client secret of the OIDC application set on the IdP"""
+
+    email_domain: Required[Annotated[str, PropertyInfo(alias="emailDomain")]]
+    """email_domain is the domain that is allowed to sign in to the organization"""
+
+    issuer_url: Required[Annotated[str, PropertyInfo(alias="issuerUrl")]]
+    """issuer_url is the URL of the IdP issuer"""
+
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
diff --git a/src/gitpod/types/organizations/sso_configuration_create_response.py b/src/gitpod/types/organizations/sso_configuration_create_response.py
new file mode 100644
index 0000000..7e85bbe
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_create_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .sso_configuration import SSOConfiguration
+
+__all__ = ["SSOConfigurationCreateResponse"]
+
+
+class SSOConfigurationCreateResponse(BaseModel):
+    sso_configuration: SSOConfiguration = FieldInfo(alias="ssoConfiguration")
+    """sso_configuration is the created SSO configuration"""
diff --git a/src/gitpod/types/organizations/sso_configuration_delete_params.py b/src/gitpod/types/organizations/sso_configuration_delete_params.py
new file mode 100644
index 0000000..0e3f7b0
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_delete_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["SSOConfigurationDeleteParams"]
+
+
+class SSOConfigurationDeleteParams(TypedDict, total=False):
+    sso_configuration_id: Required[Annotated[str, PropertyInfo(alias="ssoConfigurationId")]]
diff --git a/src/gitpod/types/organizations/sso_configuration_list_params.py b/src/gitpod/types/organizations/sso_configuration_list_params.py
new file mode 100644
index 0000000..1f4b3a6
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_list_params.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["SSOConfigurationListParams", "Pagination"]
+
+
+class SSOConfigurationListParams(TypedDict, total=False):
+    organization_id: Required[Annotated[str, PropertyInfo(alias="organizationId")]]
+    """organization_id is the ID of the organization to list SSO configurations for."""
+
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    pagination: Pagination
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/organizations/sso_configuration_retrieve_params.py b/src/gitpod/types/organizations/sso_configuration_retrieve_params.py
new file mode 100644
index 0000000..25aa634
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_retrieve_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["SSOConfigurationRetrieveParams"]
+
+
+class SSOConfigurationRetrieveParams(TypedDict, total=False):
+    sso_configuration_id: Required[Annotated[str, PropertyInfo(alias="ssoConfigurationId")]]
+    """sso_configuration_id is the ID of the SSO configuration to get"""
diff --git a/src/gitpod/types/organizations/sso_configuration_retrieve_response.py b/src/gitpod/types/organizations/sso_configuration_retrieve_response.py
new file mode 100644
index 0000000..c5e0bd3
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_retrieve_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .sso_configuration import SSOConfiguration
+
+__all__ = ["SSOConfigurationRetrieveResponse"]
+
+
+class SSOConfigurationRetrieveResponse(BaseModel):
+    sso_configuration: SSOConfiguration = FieldInfo(alias="ssoConfiguration")
+    """sso_configuration is the SSO configuration identified by the ID"""
diff --git a/src/gitpod/types/organizations/sso_configuration_state.py b/src/gitpod/types/organizations/sso_configuration_state.py
new file mode 100644
index 0000000..e866445
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_state.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["SSOConfigurationState"]
+
+SSOConfigurationState: TypeAlias = Literal[
+    "SSO_CONFIGURATION_STATE_UNSPECIFIED", "SSO_CONFIGURATION_STATE_INACTIVE", "SSO_CONFIGURATION_STATE_ACTIVE"
+]
diff --git a/src/gitpod/types/organizations/sso_configuration_update_params.py b/src/gitpod/types/organizations/sso_configuration_update_params.py
new file mode 100644
index 0000000..4af2dab
--- /dev/null
+++ b/src/gitpod/types/organizations/sso_configuration_update_params.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .sso_configuration_state import SSOConfigurationState
+
+__all__ = ["SSOConfigurationUpdateParams"]
+
+
+class SSOConfigurationUpdateParams(TypedDict, total=False):
+    sso_configuration_id: Required[Annotated[str, PropertyInfo(alias="ssoConfigurationId")]]
+    """sso_configuration_id is the ID of the SSO configuration to update"""
+
+    claims: Dict[str, str]
+    """claims are key/value pairs that defines a mapping of claims issued by the IdP."""
+
+    client_id: Annotated[Optional[str], PropertyInfo(alias="clientId")]
+    """client_id is the client ID of the SSO provider"""
+
+    client_secret: Annotated[Optional[str], PropertyInfo(alias="clientSecret")]
+    """client_secret is the client secret of the SSO provider"""
+
+    email_domain: Annotated[Optional[str], PropertyInfo(alias="emailDomain")]
+
+    issuer_url: Annotated[Optional[str], PropertyInfo(alias="issuerUrl")]
+    """issuer_url is the URL of the IdP issuer"""
+
+    state: Optional[SSOConfigurationState]
+    """state is the state of the SSO configuration"""
diff --git a/src/gitpod/types/project.py b/src/gitpod/types/project.py
new file mode 100644
index 0000000..80e2586
--- /dev/null
+++ b/src/gitpod/types/project.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.subject import Subject
+from .project_metadata import ProjectMetadata
+from .environment_initializer import EnvironmentInitializer
+from .project_environment_class import ProjectEnvironmentClass
+
+__all__ = ["Project", "UsedBy"]
+
+
+class UsedBy(BaseModel):
+    subjects: Optional[List[Subject]] = None
+    """
+    Subjects are the 10 most recent subjects who have used the project to create an
+    environment
+    """
+
+    total_subjects: Optional[int] = FieldInfo(alias="totalSubjects", default=None)
+    """Total number of unique subjects who have used the project"""
+
+
+class Project(BaseModel):
+    environment_class: ProjectEnvironmentClass = FieldInfo(alias="environmentClass")
+
+    id: Optional[str] = None
+    """id is the unique identifier for the project"""
+
+    automations_file_path: Optional[str] = FieldInfo(alias="automationsFilePath", default=None)
+    """
+    automations_file_path is the path to the automations file relative to the repo
+    root
+    """
+
+    devcontainer_file_path: Optional[str] = FieldInfo(alias="devcontainerFilePath", default=None)
+    """
+    devcontainer_file_path is the path to the devcontainer file relative to the repo
+    root
+    """
+
+    initializer: Optional[EnvironmentInitializer] = None
+    """initializer is the content initializer"""
+
+    metadata: Optional[ProjectMetadata] = None
+
+    technical_description: Optional[str] = FieldInfo(alias="technicalDescription", default=None)
+    """
+    technical_description is a detailed technical description of the project This
+    field is not returned by default in GetProject or ListProjects responses
+    """
+
+    used_by: Optional[UsedBy] = FieldInfo(alias="usedBy", default=None)
diff --git a/src/gitpod/types/project_create_from_environment_params.py b/src/gitpod/types/project_create_from_environment_params.py
new file mode 100644
index 0000000..17da3e4
--- /dev/null
+++ b/src/gitpod/types/project_create_from_environment_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["ProjectCreateFromEnvironmentParams"]
+
+
+class ProjectCreateFromEnvironmentParams(TypedDict, total=False):
+    environment_id: Annotated[str, PropertyInfo(alias="environmentId")]
+    """environment_id specifies the environment identifier"""
+
+    name: str
diff --git a/src/gitpod/types/project_create_from_environment_response.py b/src/gitpod/types/project_create_from_environment_response.py
new file mode 100644
index 0000000..5076ce2
--- /dev/null
+++ b/src/gitpod/types/project_create_from_environment_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .project import Project
+from .._models import BaseModel
+
+__all__ = ["ProjectCreateFromEnvironmentResponse"]
+
+
+class ProjectCreateFromEnvironmentResponse(BaseModel):
+    project: Optional[Project] = None
diff --git a/src/gitpod/types/project_create_params.py b/src/gitpod/types/project_create_params.py
new file mode 100644
index 0000000..ff3b8b2
--- /dev/null
+++ b/src/gitpod/types/project_create_params.py
@@ -0,0 +1,46 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .environment_initializer_param import EnvironmentInitializerParam
+from .project_environment_class_param import ProjectEnvironmentClassParam
+
+__all__ = ["ProjectCreateParams"]
+
+
+class ProjectCreateParams(TypedDict, total=False):
+    environment_class: Required[Annotated[ProjectEnvironmentClassParam, PropertyInfo(alias="environmentClass")]]
+
+    initializer: Required[EnvironmentInitializerParam]
+    """initializer is the content initializer"""
+
+    automations_file_path: Annotated[str, PropertyInfo(alias="automationsFilePath")]
+    """
+    automations_file_path is the path to the automations file relative to the repo
+    root path must not be absolute (start with a /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    devcontainer_file_path: Annotated[str, PropertyInfo(alias="devcontainerFilePath")]
+    """
+    devcontainer_file_path is the path to the devcontainer file relative to the repo
+    root path must not be absolute (start with a /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    name: str
+
+    technical_description: Annotated[str, PropertyInfo(alias="technicalDescription")]
+    """
+    technical_description is a detailed technical description of the project This
+    field is not returned by default in GetProject or ListProjects responses 8KB max
+    """
diff --git a/src/gitpod/types/project_create_response.py b/src/gitpod/types/project_create_response.py
new file mode 100644
index 0000000..81a0d7e
--- /dev/null
+++ b/src/gitpod/types/project_create_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .project import Project
+from .._models import BaseModel
+
+__all__ = ["ProjectCreateResponse"]
+
+
+class ProjectCreateResponse(BaseModel):
+    project: Optional[Project] = None
diff --git a/src/gitpod/types/project_delete_params.py b/src/gitpod/types/project_delete_params.py
new file mode 100644
index 0000000..8e7e188
--- /dev/null
+++ b/src/gitpod/types/project_delete_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["ProjectDeleteParams"]
+
+
+class ProjectDeleteParams(TypedDict, total=False):
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id specifies the project identifier"""
diff --git a/src/gitpod/types/project_environment_class.py b/src/gitpod/types/project_environment_class.py
new file mode 100644
index 0000000..a969845
--- /dev/null
+++ b/src/gitpod/types/project_environment_class.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["ProjectEnvironmentClass"]
+
+
+class ProjectEnvironmentClass(BaseModel):
+    environment_class_id: Optional[str] = FieldInfo(alias="environmentClassId", default=None)
+    """Use a fixed environment class on a given Runner.
+
+    This cannot be a local runner's environment class.
+    """
+
+    local_runner: Optional[bool] = FieldInfo(alias="localRunner", default=None)
+    """Use a local runner for the user"""
diff --git a/src/gitpod/types/project_environment_class_param.py b/src/gitpod/types/project_environment_class_param.py
new file mode 100644
index 0000000..c6e2918
--- /dev/null
+++ b/src/gitpod/types/project_environment_class_param.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["ProjectEnvironmentClassParam"]
+
+
+class ProjectEnvironmentClassParam(TypedDict, total=False):
+    environment_class_id: Annotated[str, PropertyInfo(alias="environmentClassId")]
+    """Use a fixed environment class on a given Runner.
+
+    This cannot be a local runner's environment class.
+    """
+
+    local_runner: Annotated[bool, PropertyInfo(alias="localRunner")]
+    """Use a local runner for the user"""
diff --git a/src/gitpod/types/project_list_params.py b/src/gitpod/types/project_list_params.py
new file mode 100644
index 0000000..4709a7b
--- /dev/null
+++ b/src/gitpod/types/project_list_params.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["ProjectListParams", "Filter", "Pagination"]
+
+
+class ProjectListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing organizations"""
+
+
+class Filter(TypedDict, total=False):
+    project_ids: Annotated[List[str], PropertyInfo(alias="projectIds")]
+    """project_ids filters the response to only projects with these IDs"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/project_metadata.py b/src/gitpod/types/project_metadata.py
new file mode 100644
index 0000000..5a39bda
--- /dev/null
+++ b/src/gitpod/types/project_metadata.py
@@ -0,0 +1,206 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.subject import Subject
+
+__all__ = ["ProjectMetadata"]
+
+
+class ProjectMetadata(BaseModel):
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    creator: Optional[Subject] = None
+    """creator is the identity of the project creator"""
+
+    name: Optional[str] = None
+    """name is the human readable name of the project"""
+
+    organization_id: Optional[str] = FieldInfo(alias="organizationId", default=None)
+    """organization_id is the ID of the organization that contains the environment"""
+
+    updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
diff --git a/src/gitpod/types/project_retrieve_params.py b/src/gitpod/types/project_retrieve_params.py
new file mode 100644
index 0000000..0160fa6
--- /dev/null
+++ b/src/gitpod/types/project_retrieve_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["ProjectRetrieveParams"]
+
+
+class ProjectRetrieveParams(TypedDict, total=False):
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id specifies the project identifier"""
diff --git a/src/gitpod/types/project_retrieve_response.py b/src/gitpod/types/project_retrieve_response.py
new file mode 100644
index 0000000..066a721
--- /dev/null
+++ b/src/gitpod/types/project_retrieve_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .project import Project
+from .._models import BaseModel
+
+__all__ = ["ProjectRetrieveResponse"]
+
+
+class ProjectRetrieveResponse(BaseModel):
+    project: Optional[Project] = None
diff --git a/src/gitpod/types/project_update_params.py b/src/gitpod/types/project_update_params.py
new file mode 100644
index 0000000..214d3fa
--- /dev/null
+++ b/src/gitpod/types/project_update_params.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .environment_initializer_param import EnvironmentInitializerParam
+from .project_environment_class_param import ProjectEnvironmentClassParam
+
+__all__ = ["ProjectUpdateParams"]
+
+
+class ProjectUpdateParams(TypedDict, total=False):
+    automations_file_path: Annotated[Optional[str], PropertyInfo(alias="automationsFilePath")]
+    """
+    automations_file_path is the path to the automations file relative to the repo
+    root path must not be absolute (start with a /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    devcontainer_file_path: Annotated[Optional[str], PropertyInfo(alias="devcontainerFilePath")]
+    """
+    devcontainer_file_path is the path to the devcontainer file relative to the repo
+    root path must not be absolute (start with a /):
+
+    ```
+    this.matches('^$|^[^/].*')
+    ```
+    """
+
+    environment_class: Annotated[Optional[ProjectEnvironmentClassParam], PropertyInfo(alias="environmentClass")]
+
+    initializer: Optional[EnvironmentInitializerParam]
+    """initializer is the content initializer"""
+
+    name: Optional[str]
+
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id specifies the project identifier"""
+
+    technical_description: Annotated[Optional[str], PropertyInfo(alias="technicalDescription")]
+    """
+    technical_description is a detailed technical description of the project This
+    field is not returned by default in GetProject or ListProjects responses 8KB max
+    """
diff --git a/src/gitpod/types/project_update_response.py b/src/gitpod/types/project_update_response.py
new file mode 100644
index 0000000..b426723
--- /dev/null
+++ b/src/gitpod/types/project_update_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .project import Project
+from .._models import BaseModel
+
+__all__ = ["ProjectUpdateResponse"]
+
+
+class ProjectUpdateResponse(BaseModel):
+    project: Optional[Project] = None
diff --git a/src/gitpod/types/projects/__init__.py b/src/gitpod/types/projects/__init__.py
new file mode 100644
index 0000000..f6f8d0b
--- /dev/null
+++ b/src/gitpod/types/projects/__init__.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .project_role import ProjectRole as ProjectRole
+from .project_policy import ProjectPolicy as ProjectPolicy
+from .policy_list_params import PolicyListParams as PolicyListParams
+from .policy_create_params import PolicyCreateParams as PolicyCreateParams
+from .policy_delete_params import PolicyDeleteParams as PolicyDeleteParams
+from .policy_update_params import PolicyUpdateParams as PolicyUpdateParams
+from .policy_create_response import PolicyCreateResponse as PolicyCreateResponse
+from .policy_update_response import PolicyUpdateResponse as PolicyUpdateResponse
diff --git a/src/gitpod/types/projects/policy_create_params.py b/src/gitpod/types/projects/policy_create_params.py
new file mode 100644
index 0000000..1b72ad6
--- /dev/null
+++ b/src/gitpod/types/projects/policy_create_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .project_role import ProjectRole
+
+__all__ = ["PolicyCreateParams"]
+
+
+class PolicyCreateParams(TypedDict, total=False):
+    group_id: Annotated[str, PropertyInfo(alias="groupId")]
+    """group_id specifies the group_id identifier"""
+
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id specifies the project identifier"""
+
+    role: ProjectRole
diff --git a/src/gitpod/types/projects/policy_create_response.py b/src/gitpod/types/projects/policy_create_response.py
new file mode 100644
index 0000000..0d79a48
--- /dev/null
+++ b/src/gitpod/types/projects/policy_create_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .project_policy import ProjectPolicy
+
+__all__ = ["PolicyCreateResponse"]
+
+
+class PolicyCreateResponse(BaseModel):
+    policy: Optional[ProjectPolicy] = None
diff --git a/src/gitpod/types/projects/policy_delete_params.py b/src/gitpod/types/projects/policy_delete_params.py
new file mode 100644
index 0000000..bd51f1a
--- /dev/null
+++ b/src/gitpod/types/projects/policy_delete_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PolicyDeleteParams"]
+
+
+class PolicyDeleteParams(TypedDict, total=False):
+    group_id: Annotated[str, PropertyInfo(alias="groupId")]
+    """group_id specifies the group_id identifier"""
+
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id specifies the project identifier"""
diff --git a/src/gitpod/types/projects/policy_list_params.py b/src/gitpod/types/projects/policy_list_params.py
new file mode 100644
index 0000000..9f86088
--- /dev/null
+++ b/src/gitpod/types/projects/policy_list_params.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PolicyListParams", "Pagination"]
+
+
+class PolicyListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing project policies"""
+
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id specifies the project identifier"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/projects/policy_update_params.py b/src/gitpod/types/projects/policy_update_params.py
new file mode 100644
index 0000000..c53cb50
--- /dev/null
+++ b/src/gitpod/types/projects/policy_update_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .project_role import ProjectRole
+
+__all__ = ["PolicyUpdateParams"]
+
+
+class PolicyUpdateParams(TypedDict, total=False):
+    group_id: Annotated[str, PropertyInfo(alias="groupId")]
+    """group_id specifies the group_id identifier"""
+
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id specifies the project identifier"""
+
+    role: ProjectRole
diff --git a/src/gitpod/types/projects/policy_update_response.py b/src/gitpod/types/projects/policy_update_response.py
new file mode 100644
index 0000000..96b4cce
--- /dev/null
+++ b/src/gitpod/types/projects/policy_update_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .project_policy import ProjectPolicy
+
+__all__ = ["PolicyUpdateResponse"]
+
+
+class PolicyUpdateResponse(BaseModel):
+    policy: Optional[ProjectPolicy] = None
diff --git a/src/gitpod/types/projects/project_policy.py b/src/gitpod/types/projects/project_policy.py
new file mode 100644
index 0000000..dfeb4e9
--- /dev/null
+++ b/src/gitpod/types/projects/project_policy.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .project_role import ProjectRole
+
+__all__ = ["ProjectPolicy"]
+
+
+class ProjectPolicy(BaseModel):
+    group_id: Optional[str] = FieldInfo(alias="groupId", default=None)
+
+    role: Optional[ProjectRole] = None
+    """role is the role assigned to the group"""
diff --git a/src/gitpod/types/projects/project_role.py b/src/gitpod/types/projects/project_role.py
new file mode 100644
index 0000000..e488d52
--- /dev/null
+++ b/src/gitpod/types/projects/project_role.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ProjectRole"]
+
+ProjectRole: TypeAlias = Literal[
+    "PROJECT_ROLE_UNSPECIFIED", "PROJECT_ROLE_ADMIN", "PROJECT_ROLE_USER", "PROJECT_ROLE_EDITOR"
+]
diff --git a/src/gitpod/types/resource_operation.py b/src/gitpod/types/resource_operation.py
new file mode 100644
index 0000000..3527c79
--- /dev/null
+++ b/src/gitpod/types/resource_operation.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ResourceOperation"]
+
+ResourceOperation: TypeAlias = Literal[
+    "RESOURCE_OPERATION_UNSPECIFIED",
+    "RESOURCE_OPERATION_CREATE",
+    "RESOURCE_OPERATION_UPDATE",
+    "RESOURCE_OPERATION_DELETE",
+    "RESOURCE_OPERATION_UPDATE_STATUS",
+]
diff --git a/src/gitpod/types/resource_type.py b/src/gitpod/types/resource_type.py
new file mode 100644
index 0000000..c273d47
--- /dev/null
+++ b/src/gitpod/types/resource_type.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ResourceType"]
+
+ResourceType: TypeAlias = Literal[
+    "RESOURCE_TYPE_UNSPECIFIED",
+    "RESOURCE_TYPE_ENVIRONMENT",
+    "RESOURCE_TYPE_RUNNER",
+    "RESOURCE_TYPE_PROJECT",
+    "RESOURCE_TYPE_TASK",
+    "RESOURCE_TYPE_TASK_EXECUTION",
+    "RESOURCE_TYPE_SERVICE",
+    "RESOURCE_TYPE_ORGANIZATION",
+    "RESOURCE_TYPE_USER",
+    "RESOURCE_TYPE_ENVIRONMENT_CLASS",
+    "RESOURCE_TYPE_RUNNER_SCM_INTEGRATION",
+    "RESOURCE_TYPE_HOST_AUTHENTICATION_TOKEN",
+    "RESOURCE_TYPE_GROUP",
+    "RESOURCE_TYPE_PERSONAL_ACCESS_TOKEN",
+    "RESOURCE_TYPE_USER_PREFERENCE",
+    "RESOURCE_TYPE_SERVICE_ACCOUNT",
+    "RESOURCE_TYPE_SECRET",
+    "RESOURCE_TYPE_SSO_CONFIG",
+    "RESOURCE_TYPE_DOMAIN_VERIFICATION",
+    "RESOURCE_TYPE_AGENT_EXECUTION",
+    "RESOURCE_TYPE_RUNNER_LLM_INTEGRATION",
+    "RESOURCE_TYPE_AGENT",
+    "RESOURCE_TYPE_ENVIRONMENT_SESSION",
+    "RESOURCE_TYPE_USER_SECRET",
+    "RESOURCE_TYPE_ORGANIZATION_POLICY",
+]
diff --git a/src/gitpod/types/runner.py b/src/gitpod/types/runner.py
new file mode 100644
index 0000000..ec169a3
--- /dev/null
+++ b/src/gitpod/types/runner.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .runner_kind import RunnerKind
+from .runner_spec import RunnerSpec
+from .runner_status import RunnerStatus
+from .shared.subject import Subject
+from .runner_provider import RunnerProvider
+
+__all__ = ["Runner"]
+
+
+class Runner(BaseModel):
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """Time when the Runner was created."""
+
+    creator: Optional[Subject] = None
+    """creator is the identity of the creator of the environment"""
+
+    kind: Optional[RunnerKind] = None
+    """The runner's kind"""
+
+    name: Optional[str] = None
+    """The runner's name which is shown to users"""
+
+    provider: Optional[RunnerProvider] = None
+    """The runner's provider"""
+
+    runner_id: Optional[str] = FieldInfo(alias="runnerId", default=None)
+
+    spec: Optional[RunnerSpec] = None
+    """The runner's specification"""
+
+    status: Optional[RunnerStatus] = None
+    """The runner's status"""
+
+    updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None)
+    """Time when the Runner was last udpated."""
diff --git a/src/gitpod/types/runner_capability.py b/src/gitpod/types/runner_capability.py
new file mode 100644
index 0000000..0a66872
--- /dev/null
+++ b/src/gitpod/types/runner_capability.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["RunnerCapability"]
+
+RunnerCapability: TypeAlias = Literal[
+    "RUNNER_CAPABILITY_UNSPECIFIED",
+    "RUNNER_CAPABILITY_FETCH_LOCAL_SCM_INTEGRATIONS",
+    "RUNNER_CAPABILITY_SECRET_CONTAINER_REGISTRY",
+    "RUNNER_CAPABILITY_AGENT_EXECUTION",
+    "RUNNER_CAPABILITY_ALLOW_ENV_TOKEN_POPULATION",
+    "RUNNER_CAPABILITY_DEFAULT_DEV_CONTAINER_IMAGE",
+]
diff --git a/src/gitpod/types/runner_check_authentication_for_host_params.py b/src/gitpod/types/runner_check_authentication_for_host_params.py
new file mode 100644
index 0000000..c3a5db4
--- /dev/null
+++ b/src/gitpod/types/runner_check_authentication_for_host_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RunnerCheckAuthenticationForHostParams"]
+
+
+class RunnerCheckAuthenticationForHostParams(TypedDict, total=False):
+    host: str
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
diff --git a/src/gitpod/types/runner_check_authentication_for_host_response.py b/src/gitpod/types/runner_check_authentication_for_host_response.py
new file mode 100644
index 0000000..273d871
--- /dev/null
+++ b/src/gitpod/types/runner_check_authentication_for_host_response.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["RunnerCheckAuthenticationForHostResponse", "SupportsOauth2", "SupportsPat"]
+
+
+class SupportsOauth2(BaseModel):
+    auth_url: Optional[str] = FieldInfo(alias="authUrl", default=None)
+    """auth_url is the URL where users can authenticate"""
+
+    docs_url: Optional[str] = FieldInfo(alias="docsUrl", default=None)
+    """docs_url is the URL to the documentation explaining this authentication method"""
+
+
+class SupportsPat(BaseModel):
+    create_url: Optional[str] = FieldInfo(alias="createUrl", default=None)
+    """create_url is the URL where users can create a new Personal Access Token"""
+
+    docs_url: Optional[str] = FieldInfo(alias="docsUrl", default=None)
+    """docs_url is the URL to the documentation explaining PAT usage for this host"""
+
+    example: Optional[str] = None
+    """example is an example of a Personal Access Token"""
+
+    required_scopes: Optional[List[str]] = FieldInfo(alias="requiredScopes", default=None)
+    """
+    required_scopes is the list of permissions required for the Personal Access
+    Token
+    """
+
+
+class RunnerCheckAuthenticationForHostResponse(BaseModel):
+    authenticated: Optional[bool] = None
+
+    authentication_url: Optional[str] = FieldInfo(alias="authenticationUrl", default=None)
+
+    pat_supported: Optional[bool] = FieldInfo(alias="patSupported", default=None)
+
+    scm_id: Optional[str] = FieldInfo(alias="scmId", default=None)
+    """scm_id is the unique identifier of the SCM provider"""
+
+    scm_name: Optional[str] = FieldInfo(alias="scmName", default=None)
+    """
+    scm_name is the human-readable name of the SCM provider (e.g., "GitHub",
+    "GitLab")
+    """
+
+    supports_oauth2: Optional[SupportsOauth2] = FieldInfo(alias="supportsOauth2", default=None)
+    """supports_oauth2 indicates that the host supports OAuth2 authentication"""
+
+    supports_pat: Optional[SupportsPat] = FieldInfo(alias="supportsPat", default=None)
+    """
+    supports_pat indicates that the host supports Personal Access Token
+    authentication
+    """
diff --git a/src/gitpod/types/runner_configuration.py b/src/gitpod/types/runner_configuration.py
new file mode 100644
index 0000000..4f91d55
--- /dev/null
+++ b/src/gitpod/types/runner_configuration.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .log_level import LogLevel
+from .metrics_configuration import MetricsConfiguration
+from .runner_release_channel import RunnerReleaseChannel
+
+__all__ = ["RunnerConfiguration"]
+
+
+class RunnerConfiguration(BaseModel):
+    auto_update: Optional[bool] = FieldInfo(alias="autoUpdate", default=None)
+    """auto_update indicates whether the runner should automatically update itself."""
+
+    devcontainer_image_cache_enabled: Optional[bool] = FieldInfo(alias="devcontainerImageCacheEnabled", default=None)
+    """
+    devcontainer_image_cache_enabled controls whether the devcontainer build cache
+    is enabled for this runner. Only takes effect on supported runners, currently
+    only AWS EC2 runners.
+    """
+
+    log_level: Optional[LogLevel] = FieldInfo(alias="logLevel", default=None)
+    """log_level is the log level for the runner"""
+
+    metrics: Optional[MetricsConfiguration] = None
+    """metrics contains configuration for the runner's metrics collection"""
+
+    region: Optional[str] = None
+    """
+    Region to deploy the runner in, if applicable. This is mainly used for remote
+    runners, and is only a hint. The runner may be deployed in a different region.
+    See the runner's status for the actual region.
+    """
+
+    release_channel: Optional[RunnerReleaseChannel] = FieldInfo(alias="releaseChannel", default=None)
+    """The release channel the runner is on"""
diff --git a/src/gitpod/types/runner_configuration_param.py b/src/gitpod/types/runner_configuration_param.py
new file mode 100644
index 0000000..7ba49ee
--- /dev/null
+++ b/src/gitpod/types/runner_configuration_param.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .log_level import LogLevel
+from .runner_release_channel import RunnerReleaseChannel
+from .metrics_configuration_param import MetricsConfigurationParam
+
+__all__ = ["RunnerConfigurationParam"]
+
+
+class RunnerConfigurationParam(TypedDict, total=False):
+    auto_update: Annotated[bool, PropertyInfo(alias="autoUpdate")]
+    """auto_update indicates whether the runner should automatically update itself."""
+
+    devcontainer_image_cache_enabled: Annotated[bool, PropertyInfo(alias="devcontainerImageCacheEnabled")]
+    """
+    devcontainer_image_cache_enabled controls whether the devcontainer build cache
+    is enabled for this runner. Only takes effect on supported runners, currently
+    only AWS EC2 runners.
+    """
+
+    log_level: Annotated[LogLevel, PropertyInfo(alias="logLevel")]
+    """log_level is the log level for the runner"""
+
+    metrics: MetricsConfigurationParam
+    """metrics contains configuration for the runner's metrics collection"""
+
+    region: str
+    """
+    Region to deploy the runner in, if applicable. This is mainly used for remote
+    runners, and is only a hint. The runner may be deployed in a different region.
+    See the runner's status for the actual region.
+    """
+
+    release_channel: Annotated[RunnerReleaseChannel, PropertyInfo(alias="releaseChannel")]
+    """The release channel the runner is on"""
diff --git a/src/gitpod/types/runner_create_params.py b/src/gitpod/types/runner_create_params.py
new file mode 100644
index 0000000..6834fa3
--- /dev/null
+++ b/src/gitpod/types/runner_create_params.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .runner_kind import RunnerKind
+from .runner_provider import RunnerProvider
+from .runner_spec_param import RunnerSpecParam
+
+__all__ = ["RunnerCreateParams"]
+
+
+class RunnerCreateParams(TypedDict, total=False):
+    kind: RunnerKind
+    """The runner's kind This field is optional and here for backwards-compatibility.
+
+    Use the provider field instead. If provider is set, the runner's kind will be
+    deduced from the provider. Only one of kind and provider must be set.
+    """
+
+    name: str
+    """The runner name for humans"""
+
+    provider: RunnerProvider
+    """
+    The specific implementation type of the runner This field is optional for
+    backwards compatibility but will be required in the future. When specified, kind
+    must not be specified (will be deduced from provider)
+    """
+
+    runner_manager_id: Annotated[str, PropertyInfo(alias="runnerManagerId")]
+    """
+    The runner manager id specifies the runner manager for the managed runner. This
+    field is mandatory for managed runners, otheriwse should not be set.
+    """
+
+    spec: RunnerSpecParam
diff --git a/src/gitpod/types/runner_create_response.py b/src/gitpod/types/runner_create_response.py
new file mode 100644
index 0000000..2f6821f
--- /dev/null
+++ b/src/gitpod/types/runner_create_response.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .runner import Runner
+from .._models import BaseModel
+
+__all__ = ["RunnerCreateResponse"]
+
+
+class RunnerCreateResponse(BaseModel):
+    runner: Runner
+
+    access_token: Optional[str] = FieldInfo(alias="accessToken", default=None)
+    """deprecated, will be removed. Use exchange_token instead."""
+
+    exchange_token: Optional[str] = FieldInfo(alias="exchangeToken", default=None)
+    """
+    exchange_token is a one-time use token that should be exchanged by the runner
+    for an access token, using the IdentityService.ExchangeToken rpc. The token
+    expires after 24 hours.
+    """
diff --git a/src/gitpod/types/runner_create_runner_token_params.py b/src/gitpod/types/runner_create_runner_token_params.py
new file mode 100644
index 0000000..8bf7b70
--- /dev/null
+++ b/src/gitpod/types/runner_create_runner_token_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RunnerCreateRunnerTokenParams"]
+
+
+class RunnerCreateRunnerTokenParams(TypedDict, total=False):
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
diff --git a/src/gitpod/types/runner_create_runner_token_response.py b/src/gitpod/types/runner_create_runner_token_response.py
new file mode 100644
index 0000000..0f793a3
--- /dev/null
+++ b/src/gitpod/types/runner_create_runner_token_response.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["RunnerCreateRunnerTokenResponse"]
+
+
+class RunnerCreateRunnerTokenResponse(BaseModel):
+    access_token: Optional[str] = FieldInfo(alias="accessToken", default=None)
+    """deprecated, will be removed. Use exchange_token instead."""
+
+    exchange_token: Optional[str] = FieldInfo(alias="exchangeToken", default=None)
+    """
+    exchange_token is a one-time use token that should be exchanged by the runner
+    for an access token, using the IdentityService.ExchangeToken rpc. The token
+    expires after 24 hours.
+    """
diff --git a/src/gitpod/types/runner_delete_params.py b/src/gitpod/types/runner_delete_params.py
new file mode 100644
index 0000000..a532268
--- /dev/null
+++ b/src/gitpod/types/runner_delete_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RunnerDeleteParams"]
+
+
+class RunnerDeleteParams(TypedDict, total=False):
+    force: bool
+    """
+    force indicates whether the runner should be deleted forcefully. When force
+    deleting a Runner, all Environments on the runner are also force deleted and
+    regular Runner lifecycle is not respected. Force deleting can result in data
+    loss.
+    """
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
diff --git a/src/gitpod/types/runner_kind.py b/src/gitpod/types/runner_kind.py
new file mode 100644
index 0000000..2302dbe
--- /dev/null
+++ b/src/gitpod/types/runner_kind.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["RunnerKind"]
+
+RunnerKind: TypeAlias = Literal[
+    "RUNNER_KIND_UNSPECIFIED", "RUNNER_KIND_LOCAL", "RUNNER_KIND_REMOTE", "RUNNER_KIND_LOCAL_CONFIGURATION"
+]
diff --git a/src/gitpod/types/runner_list_params.py b/src/gitpod/types/runner_list_params.py
new file mode 100644
index 0000000..7d2a986
--- /dev/null
+++ b/src/gitpod/types/runner_list_params.py
@@ -0,0 +1,48 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .runner_kind import RunnerKind
+from .runner_provider import RunnerProvider
+
+__all__ = ["RunnerListParams", "Filter", "Pagination"]
+
+
+class RunnerListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing runners"""
+
+
+class Filter(TypedDict, total=False):
+    creator_ids: Annotated[List[str], PropertyInfo(alias="creatorIds")]
+    """creator_ids filters the response to only runner created by specified users"""
+
+    kinds: List[RunnerKind]
+    """kinds filters the response to only runners of the specified kinds"""
+
+    providers: List[RunnerProvider]
+    """providers filters the response to only runners of the specified providers"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/runner_parse_context_url_params.py b/src/gitpod/types/runner_parse_context_url_params.py
new file mode 100644
index 0000000..986d37b
--- /dev/null
+++ b/src/gitpod/types/runner_parse_context_url_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RunnerParseContextURLParams"]
+
+
+class RunnerParseContextURLParams(TypedDict, total=False):
+    context_url: Annotated[str, PropertyInfo(alias="contextUrl")]
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
diff --git a/src/gitpod/types/runner_parse_context_url_response.py b/src/gitpod/types/runner_parse_context_url_response.py
new file mode 100644
index 0000000..e34f770
--- /dev/null
+++ b/src/gitpod/types/runner_parse_context_url_response.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["RunnerParseContextURLResponse", "Git"]
+
+
+class Git(BaseModel):
+    branch: Optional[str] = None
+
+    clone_url: Optional[str] = FieldInfo(alias="cloneUrl", default=None)
+
+    commit: Optional[str] = None
+
+    host: Optional[str] = None
+
+    owner: Optional[str] = None
+
+    repo: Optional[str] = None
+
+    upstream_remote_url: Optional[str] = FieldInfo(alias="upstreamRemoteUrl", default=None)
+
+
+class RunnerParseContextURLResponse(BaseModel):
+    git: Optional[Git] = None
+
+    original_context_url: Optional[str] = FieldInfo(alias="originalContextUrl", default=None)
+
+    project_ids: Optional[List[str]] = FieldInfo(alias="projectIds", default=None)
+    """project_ids is a list of projects to which the context URL belongs to."""
diff --git a/src/gitpod/types/runner_phase.py b/src/gitpod/types/runner_phase.py
new file mode 100644
index 0000000..b7cd3e9
--- /dev/null
+++ b/src/gitpod/types/runner_phase.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["RunnerPhase"]
+
+RunnerPhase: TypeAlias = Literal[
+    "RUNNER_PHASE_UNSPECIFIED",
+    "RUNNER_PHASE_CREATED",
+    "RUNNER_PHASE_INACTIVE",
+    "RUNNER_PHASE_ACTIVE",
+    "RUNNER_PHASE_DELETING",
+    "RUNNER_PHASE_DELETED",
+    "RUNNER_PHASE_DEGRADED",
+]
diff --git a/src/gitpod/types/runner_provider.py b/src/gitpod/types/runner_provider.py
new file mode 100644
index 0000000..8f8efef
--- /dev/null
+++ b/src/gitpod/types/runner_provider.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["RunnerProvider"]
+
+RunnerProvider: TypeAlias = Literal[
+    "RUNNER_PROVIDER_UNSPECIFIED",
+    "RUNNER_PROVIDER_AWS_EC2",
+    "RUNNER_PROVIDER_LINUX_HOST",
+    "RUNNER_PROVIDER_DESKTOP_MAC",
+    "RUNNER_PROVIDER_MANAGED",
+]
diff --git a/src/gitpod/types/runner_release_channel.py b/src/gitpod/types/runner_release_channel.py
new file mode 100644
index 0000000..c85e07a
--- /dev/null
+++ b/src/gitpod/types/runner_release_channel.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["RunnerReleaseChannel"]
+
+RunnerReleaseChannel: TypeAlias = Literal[
+    "RUNNER_RELEASE_CHANNEL_UNSPECIFIED", "RUNNER_RELEASE_CHANNEL_STABLE", "RUNNER_RELEASE_CHANNEL_LATEST"
+]
diff --git a/src/gitpod/types/runner_retrieve_params.py b/src/gitpod/types/runner_retrieve_params.py
new file mode 100644
index 0000000..409766f
--- /dev/null
+++ b/src/gitpod/types/runner_retrieve_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RunnerRetrieveParams"]
+
+
+class RunnerRetrieveParams(TypedDict, total=False):
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
diff --git a/src/gitpod/types/runner_retrieve_response.py b/src/gitpod/types/runner_retrieve_response.py
new file mode 100644
index 0000000..b4ec6d8
--- /dev/null
+++ b/src/gitpod/types/runner_retrieve_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .runner import Runner
+from .._models import BaseModel
+
+__all__ = ["RunnerRetrieveResponse"]
+
+
+class RunnerRetrieveResponse(BaseModel):
+    runner: Runner
diff --git a/src/gitpod/types/runner_spec.py b/src/gitpod/types/runner_spec.py
new file mode 100644
index 0000000..9fb4811
--- /dev/null
+++ b/src/gitpod/types/runner_spec.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .runner_phase import RunnerPhase
+from .runner_configuration import RunnerConfiguration
+
+__all__ = ["RunnerSpec"]
+
+
+class RunnerSpec(BaseModel):
+    configuration: Optional[RunnerConfiguration] = None
+    """The runner's configuration"""
+
+    desired_phase: Optional[RunnerPhase] = FieldInfo(alias="desiredPhase", default=None)
+    """RunnerPhase represents the phase a runner is in"""
diff --git a/src/gitpod/types/runner_spec_param.py b/src/gitpod/types/runner_spec_param.py
new file mode 100644
index 0000000..c356eb3
--- /dev/null
+++ b/src/gitpod/types/runner_spec_param.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .runner_phase import RunnerPhase
+from .runner_configuration_param import RunnerConfigurationParam
+
+__all__ = ["RunnerSpecParam"]
+
+
+class RunnerSpecParam(TypedDict, total=False):
+    configuration: RunnerConfigurationParam
+    """The runner's configuration"""
+
+    desired_phase: Annotated[RunnerPhase, PropertyInfo(alias="desiredPhase")]
+    """RunnerPhase represents the phase a runner is in"""
diff --git a/src/gitpod/types/runner_status.py b/src/gitpod/types/runner_status.py
new file mode 100644
index 0000000..a302c70
--- /dev/null
+++ b/src/gitpod/types/runner_status.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .gateway_info import GatewayInfo
+from .runner_phase import RunnerPhase
+from .runner_capability import RunnerCapability
+from .shared.field_value import FieldValue
+
+__all__ = ["RunnerStatus"]
+
+
+class RunnerStatus(BaseModel):
+    additional_info: Optional[List[FieldValue]] = FieldInfo(alias="additionalInfo", default=None)
+    """additional_info contains additional information about the runner, e.g.
+
+    a CloudFormation stack URL.
+    """
+
+    capabilities: Optional[List[RunnerCapability]] = None
+    """capabilities is a list of capabilities the runner supports."""
+
+    gateway_info: Optional[GatewayInfo] = FieldInfo(alias="gatewayInfo", default=None)
+    """gateway_info is information about the gateway to which the runner is connected."""
+
+    log_url: Optional[str] = FieldInfo(alias="logUrl", default=None)
+
+    message: Optional[str] = None
+    """
+    The runner's reported message which is shown to users. This message adds more
+    context to the runner's phase.
+    """
+
+    phase: Optional[RunnerPhase] = None
+    """The runner's reported phase"""
+
+    region: Optional[str] = None
+    """region is the region the runner is running in, if applicable."""
+
+    system_details: Optional[str] = FieldInfo(alias="systemDetails", default=None)
+
+    updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None)
+    """Time when the status was last updated."""
+
+    version: Optional[str] = None
diff --git a/src/gitpod/types/runner_update_params.py b/src/gitpod/types/runner_update_params.py
new file mode 100644
index 0000000..4bef0fe
--- /dev/null
+++ b/src/gitpod/types/runner_update_params.py
@@ -0,0 +1,77 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .log_level import LogLevel
+from .runner_phase import RunnerPhase
+from .runner_release_channel import RunnerReleaseChannel
+
+__all__ = ["RunnerUpdateParams", "Spec", "SpecConfiguration", "SpecConfigurationMetrics"]
+
+
+class RunnerUpdateParams(TypedDict, total=False):
+    name: Optional[str]
+    """The runner's name which is shown to users"""
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+    """runner_id specifies which runner to be updated.
+
+    +required
+    """
+
+    spec: Optional[Spec]
+
+
+class SpecConfigurationMetrics(TypedDict, total=False):
+    enabled: Optional[bool]
+    """enabled indicates whether the runner should collect metrics"""
+
+    password: Optional[str]
+    """password is the password to use for the metrics collector"""
+
+    url: Optional[str]
+    """url is the URL of the metrics collector"""
+
+    username: Optional[str]
+    """username is the username to use for the metrics collector"""
+
+
+class SpecConfiguration(TypedDict, total=False):
+    auto_update: Annotated[Optional[bool], PropertyInfo(alias="autoUpdate")]
+    """auto_update indicates whether the runner should automatically update itself."""
+
+    devcontainer_image_cache_enabled: Annotated[Optional[bool], PropertyInfo(alias="devcontainerImageCacheEnabled")]
+    """
+    devcontainer_image_cache_enabled controls whether the shared devcontainer build
+    cache is enabled for this runner.
+    """
+
+    log_level: Annotated[Optional[LogLevel], PropertyInfo(alias="logLevel")]
+    """log_level is the log level for the runner"""
+
+    metrics: Optional[SpecConfigurationMetrics]
+    """metrics contains configuration for the runner's metrics collection"""
+
+    release_channel: Annotated[Optional[RunnerReleaseChannel], PropertyInfo(alias="releaseChannel")]
+    """The release channel the runner is on"""
+
+
+class Spec(TypedDict, total=False):
+    configuration: Optional[SpecConfiguration]
+
+    desired_phase: Annotated[Optional[RunnerPhase], PropertyInfo(alias="desiredPhase")]
+    """
+    desired_phase can currently only be updated on local-configuration runners, to
+    toggle whether local runners are allowed for running environments in the
+    organization. Set to:
+
+    - ACTIVE to enable local runners.
+    - INACTIVE to disable all local runners. Existing local runners and their
+      environments will stop, and cannot be started again until the desired_phase is
+      set to ACTIVE. Use this carefully, as it will affect all users in the
+      organization who use local runners.
+    """
diff --git a/src/gitpod/types/runners/__init__.py b/src/gitpod/types/runners/__init__.py
new file mode 100644
index 0000000..4d19ee2
--- /dev/null
+++ b/src/gitpod/types/runners/__init__.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .runner_role import RunnerRole as RunnerRole
+from .runner_policy import RunnerPolicy as RunnerPolicy
+from .policy_list_params import PolicyListParams as PolicyListParams
+from .policy_create_params import PolicyCreateParams as PolicyCreateParams
+from .policy_delete_params import PolicyDeleteParams as PolicyDeleteParams
+from .policy_update_params import PolicyUpdateParams as PolicyUpdateParams
+from .field_validation_error import FieldValidationError as FieldValidationError
+from .policy_create_response import PolicyCreateResponse as PolicyCreateResponse
+from .policy_update_response import PolicyUpdateResponse as PolicyUpdateResponse
+from .configuration_validate_params import ConfigurationValidateParams as ConfigurationValidateParams
+from .configuration_validate_response import ConfigurationValidateResponse as ConfigurationValidateResponse
+from .scm_integration_validation_result import ScmIntegrationValidationResult as ScmIntegrationValidationResult
+from .environment_class_validation_result import EnvironmentClassValidationResult as EnvironmentClassValidationResult
diff --git a/src/gitpod/types/runners/configuration_validate_params.py b/src/gitpod/types/runners/configuration_validate_params.py
new file mode 100644
index 0000000..8afdbe5
--- /dev/null
+++ b/src/gitpod/types/runners/configuration_validate_params.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from typing_extensions import Annotated, TypedDict
+
+from ..._types import Base64FileInput
+from ..._utils import PropertyInfo
+from ..._models import set_pydantic_config
+from ..shared_params.environment_class import EnvironmentClass
+
+__all__ = ["ConfigurationValidateParams", "ScmIntegration"]
+
+
+class ConfigurationValidateParams(TypedDict, total=False):
+    environment_class: Annotated[EnvironmentClass, PropertyInfo(alias="environmentClass")]
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+
+    scm_integration: Annotated[ScmIntegration, PropertyInfo(alias="scmIntegration")]
+
+
+class ScmIntegration(TypedDict, total=False):
+    id: str
+    """id is the unique identifier of the SCM integration"""
+
+    host: str
+
+    issuer_url: Annotated[Optional[str], PropertyInfo(alias="issuerUrl")]
+    """
+    issuer_url can be set to override the authentication provider URL, if it doesn't
+    match the SCM host.
+    """
+
+    oauth_client_id: Annotated[Optional[str], PropertyInfo(alias="oauthClientId")]
+    """
+    oauth_client_id is the OAuth app's client ID, if OAuth is configured. If
+    configured, oauth_client_secret must also be set.
+    """
+
+    oauth_encrypted_client_secret: Annotated[
+        Union[str, Base64FileInput], PropertyInfo(alias="oauthEncryptedClientSecret", format="base64")
+    ]
+    """
+    oauth_encrypted_client_secret is the OAuth app's client secret encrypted with
+    the runner's public key, if OAuth is configured. This can be used to e.g.
+    validate an already encrypted client secret of an existing SCM integration.
+    """
+
+    oauth_plaintext_client_secret: Annotated[str, PropertyInfo(alias="oauthPlaintextClientSecret")]
+    """
+    oauth_plaintext_client_secret is the OAuth app's client secret in clear text, if
+    OAuth is configured. This can be set to validate any new client secret before it
+    is encrypted and stored. This value will not be stored and get encrypted with
+    the runner's public key before passing it to the runner.
+    """
+
+    pat: bool
+
+    scm_id: Annotated[str, PropertyInfo(alias="scmId")]
+    """
+    scm_id references the scm_id in the runner's configuration schema that this
+    integration is for
+    """
+
+
+set_pydantic_config(ScmIntegration, {"arbitrary_types_allowed": True})
diff --git a/src/gitpod/types/runners/configuration_validate_response.py b/src/gitpod/types/runners/configuration_validate_response.py
new file mode 100644
index 0000000..3009132
--- /dev/null
+++ b/src/gitpod/types/runners/configuration_validate_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .scm_integration_validation_result import ScmIntegrationValidationResult
+from .environment_class_validation_result import EnvironmentClassValidationResult
+
+__all__ = ["ConfigurationValidateResponse"]
+
+
+class ConfigurationValidateResponse(BaseModel):
+    environment_class: Optional[EnvironmentClassValidationResult] = FieldInfo(alias="environmentClass", default=None)
+
+    scm_integration: Optional[ScmIntegrationValidationResult] = FieldInfo(alias="scmIntegration", default=None)
diff --git a/src/gitpod/types/runners/configurations/__init__.py b/src/gitpod/types/runners/configurations/__init__.py
new file mode 100644
index 0000000..5a36d94
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/__init__.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .scm_integration import ScmIntegration as ScmIntegration
+from .schema_retrieve_params import SchemaRetrieveParams as SchemaRetrieveParams
+from .schema_retrieve_response import SchemaRetrieveResponse as SchemaRetrieveResponse
+from .host_authentication_token import HostAuthenticationToken as HostAuthenticationToken
+from .runner_configuration_schema import RunnerConfigurationSchema as RunnerConfigurationSchema
+from .scm_integration_list_params import ScmIntegrationListParams as ScmIntegrationListParams
+from .scm_integration_oauth_config import ScmIntegrationOAuthConfig as ScmIntegrationOAuthConfig
+from .environment_class_list_params import EnvironmentClassListParams as EnvironmentClassListParams
+from .scm_integration_create_params import ScmIntegrationCreateParams as ScmIntegrationCreateParams
+from .scm_integration_delete_params import ScmIntegrationDeleteParams as ScmIntegrationDeleteParams
+from .scm_integration_update_params import ScmIntegrationUpdateParams as ScmIntegrationUpdateParams
+from .environment_class_create_params import EnvironmentClassCreateParams as EnvironmentClassCreateParams
+from .environment_class_update_params import EnvironmentClassUpdateParams as EnvironmentClassUpdateParams
+from .scm_integration_create_response import ScmIntegrationCreateResponse as ScmIntegrationCreateResponse
+from .scm_integration_retrieve_params import ScmIntegrationRetrieveParams as ScmIntegrationRetrieveParams
+from .host_authentication_token_source import HostAuthenticationTokenSource as HostAuthenticationTokenSource
+from .environment_class_create_response import EnvironmentClassCreateResponse as EnvironmentClassCreateResponse
+from .environment_class_retrieve_params import EnvironmentClassRetrieveParams as EnvironmentClassRetrieveParams
+from .scm_integration_retrieve_response import ScmIntegrationRetrieveResponse as ScmIntegrationRetrieveResponse
+from .environment_class_retrieve_response import EnvironmentClassRetrieveResponse as EnvironmentClassRetrieveResponse
+from .host_authentication_token_list_params import (
+    HostAuthenticationTokenListParams as HostAuthenticationTokenListParams,
+)
+from .host_authentication_token_create_params import (
+    HostAuthenticationTokenCreateParams as HostAuthenticationTokenCreateParams,
+)
+from .host_authentication_token_delete_params import (
+    HostAuthenticationTokenDeleteParams as HostAuthenticationTokenDeleteParams,
+)
+from .host_authentication_token_update_params import (
+    HostAuthenticationTokenUpdateParams as HostAuthenticationTokenUpdateParams,
+)
+from .host_authentication_token_create_response import (
+    HostAuthenticationTokenCreateResponse as HostAuthenticationTokenCreateResponse,
+)
+from .host_authentication_token_retrieve_params import (
+    HostAuthenticationTokenRetrieveParams as HostAuthenticationTokenRetrieveParams,
+)
+from .host_authentication_token_retrieve_response import (
+    HostAuthenticationTokenRetrieveResponse as HostAuthenticationTokenRetrieveResponse,
+)
diff --git a/src/gitpod/types/runners/configurations/environment_class_create_params.py b/src/gitpod/types/runners/configurations/environment_class_create_params.py
new file mode 100644
index 0000000..1f624b0
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/environment_class_create_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from ...shared_params.field_value import FieldValue
+
+__all__ = ["EnvironmentClassCreateParams"]
+
+
+class EnvironmentClassCreateParams(TypedDict, total=False):
+    configuration: Iterable[FieldValue]
+
+    description: str
+
+    display_name: Annotated[str, PropertyInfo(alias="displayName")]
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
diff --git a/src/gitpod/types/runners/configurations/environment_class_create_response.py b/src/gitpod/types/runners/configurations/environment_class_create_response.py
new file mode 100644
index 0000000..47198a0
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/environment_class_create_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+
+__all__ = ["EnvironmentClassCreateResponse"]
+
+
+class EnvironmentClassCreateResponse(BaseModel):
+    id: Optional[str] = None
diff --git a/src/gitpod/types/runners/configurations/environment_class_list_params.py b/src/gitpod/types/runners/configurations/environment_class_list_params.py
new file mode 100644
index 0000000..14cdf32
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/environment_class_list_params.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from ...runner_kind import RunnerKind
+from ...runner_provider import RunnerProvider
+
+__all__ = ["EnvironmentClassListParams", "Filter", "Pagination"]
+
+
+class EnvironmentClassListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing environment classes"""
+
+
+class Filter(TypedDict, total=False):
+    can_create_environments: Annotated[Optional[bool], PropertyInfo(alias="canCreateEnvironments")]
+    """
+    can_create_environments filters the response to only environment classes that
+    can be used to create new environments by the caller. Unlike enabled, which
+    indicates general availability, this ensures the caller only sees environment
+    classes they are allowed to use.
+    """
+
+    enabled: Optional[bool]
+    """
+    enabled filters the response to only enabled or disabled environment classes. If
+    not set, all environment classes are returned.
+    """
+
+    runner_ids: Annotated[List[str], PropertyInfo(alias="runnerIds")]
+    """runner_ids filters the response to only EnvironmentClasses of these Runner IDs"""
+
+    runner_kinds: Annotated[List[RunnerKind], PropertyInfo(alias="runnerKinds")]
+    """
+    runner_kind filters the response to only environment classes from runners of
+    these kinds.
+    """
+
+    runner_providers: Annotated[List[RunnerProvider], PropertyInfo(alias="runnerProviders")]
+    """
+    runner_providers filters the response to only environment classes from runners
+    of these providers.
+    """
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/runners/configurations/environment_class_retrieve_params.py b/src/gitpod/types/runners/configurations/environment_class_retrieve_params.py
new file mode 100644
index 0000000..51b92e1
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/environment_class_retrieve_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["EnvironmentClassRetrieveParams"]
+
+
+class EnvironmentClassRetrieveParams(TypedDict, total=False):
+    environment_class_id: Annotated[str, PropertyInfo(alias="environmentClassId")]
diff --git a/src/gitpod/types/runners/configurations/environment_class_retrieve_response.py b/src/gitpod/types/runners/configurations/environment_class_retrieve_response.py
new file mode 100644
index 0000000..36ad250
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/environment_class_retrieve_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from ...shared.environment_class import EnvironmentClass
+
+__all__ = ["EnvironmentClassRetrieveResponse"]
+
+
+class EnvironmentClassRetrieveResponse(BaseModel):
+    environment_class: Optional[EnvironmentClass] = FieldInfo(alias="environmentClass", default=None)
diff --git a/src/gitpod/types/runners/configurations/environment_class_update_params.py b/src/gitpod/types/runners/configurations/environment_class_update_params.py
new file mode 100644
index 0000000..e161361
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/environment_class_update_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["EnvironmentClassUpdateParams"]
+
+
+class EnvironmentClassUpdateParams(TypedDict, total=False):
+    description: Optional[str]
+
+    display_name: Annotated[Optional[str], PropertyInfo(alias="displayName")]
+
+    enabled: Optional[bool]
+
+    environment_class_id: Annotated[str, PropertyInfo(alias="environmentClassId")]
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token.py b/src/gitpod/types/runners/configurations/host_authentication_token.py
new file mode 100644
index 0000000..22e5760
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token.py
@@ -0,0 +1,115 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from .host_authentication_token_source import HostAuthenticationTokenSource
+
+__all__ = ["HostAuthenticationToken"]
+
+
+class HostAuthenticationToken(BaseModel):
+    id: str
+
+    expires_at: Optional[datetime] = FieldInfo(alias="expiresAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    host: Optional[str] = None
+
+    runner_id: Optional[str] = FieldInfo(alias="runnerId", default=None)
+
+    source: Optional[HostAuthenticationTokenSource] = None
+
+    user_id: Optional[str] = FieldInfo(alias="userId", default=None)
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_create_params.py b/src/gitpod/types/runners/configurations/host_authentication_token_create_params.py
new file mode 100644
index 0000000..f0ba68e
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_create_params.py
@@ -0,0 +1,118 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from .host_authentication_token_source import HostAuthenticationTokenSource
+
+__all__ = ["HostAuthenticationTokenCreateParams"]
+
+
+class HostAuthenticationTokenCreateParams(TypedDict, total=False):
+    token: str
+
+    expires_at: Annotated[Union[str, datetime], PropertyInfo(alias="expiresAt", format="iso8601")]
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    host: str
+
+    refresh_token: Annotated[str, PropertyInfo(alias="refreshToken")]
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+
+    source: HostAuthenticationTokenSource
+
+    user_id: Annotated[str, PropertyInfo(alias="userId")]
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_create_response.py b/src/gitpod/types/runners/configurations/host_authentication_token_create_response.py
new file mode 100644
index 0000000..143bdbb
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_create_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ...._models import BaseModel
+from .host_authentication_token import HostAuthenticationToken
+
+__all__ = ["HostAuthenticationTokenCreateResponse"]
+
+
+class HostAuthenticationTokenCreateResponse(BaseModel):
+    token: HostAuthenticationToken
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_delete_params.py b/src/gitpod/types/runners/configurations/host_authentication_token_delete_params.py
new file mode 100644
index 0000000..f7de02b
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_delete_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["HostAuthenticationTokenDeleteParams"]
+
+
+class HostAuthenticationTokenDeleteParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_list_params.py b/src/gitpod/types/runners/configurations/host_authentication_token_list_params.py
new file mode 100644
index 0000000..2576f40
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_list_params.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["HostAuthenticationTokenListParams", "Filter", "Pagination"]
+
+
+class HostAuthenticationTokenListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+
+
+class Filter(TypedDict, total=False):
+    runner_id: Annotated[Optional[str], PropertyInfo(alias="runnerId")]
+
+    user_id: Annotated[Optional[str], PropertyInfo(alias="userId")]
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_retrieve_params.py b/src/gitpod/types/runners/configurations/host_authentication_token_retrieve_params.py
new file mode 100644
index 0000000..c544285
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_retrieve_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["HostAuthenticationTokenRetrieveParams"]
+
+
+class HostAuthenticationTokenRetrieveParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_retrieve_response.py b/src/gitpod/types/runners/configurations/host_authentication_token_retrieve_response.py
new file mode 100644
index 0000000..ee629a1
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_retrieve_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ...._models import BaseModel
+from .host_authentication_token import HostAuthenticationToken
+
+__all__ = ["HostAuthenticationTokenRetrieveResponse"]
+
+
+class HostAuthenticationTokenRetrieveResponse(BaseModel):
+    token: HostAuthenticationToken
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_source.py b/src/gitpod/types/runners/configurations/host_authentication_token_source.py
new file mode 100644
index 0000000..c610d07
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_source.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["HostAuthenticationTokenSource"]
+
+HostAuthenticationTokenSource: TypeAlias = Literal[
+    "HOST_AUTHENTICATION_TOKEN_SOURCE_UNSPECIFIED",
+    "HOST_AUTHENTICATION_TOKEN_SOURCE_OAUTH",
+    "HOST_AUTHENTICATION_TOKEN_SOURCE_PAT",
+]
diff --git a/src/gitpod/types/runners/configurations/host_authentication_token_update_params.py b/src/gitpod/types/runners/configurations/host_authentication_token_update_params.py
new file mode 100644
index 0000000..50dfcd2
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/host_authentication_token_update_params.py
@@ -0,0 +1,111 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["HostAuthenticationTokenUpdateParams"]
+
+
+class HostAuthenticationTokenUpdateParams(TypedDict, total=False):
+    id: str
+
+    token: Optional[str]
+
+    expires_at: Annotated[Union[str, datetime, None], PropertyInfo(alias="expiresAt", format="iso8601")]
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    refresh_token: Annotated[Optional[str], PropertyInfo(alias="refreshToken")]
diff --git a/src/gitpod/types/runners/configurations/runner_configuration_schema.py b/src/gitpod/types/runners/configurations/runner_configuration_schema.py
new file mode 100644
index 0000000..caeccd7
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/runner_configuration_schema.py
@@ -0,0 +1,217 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import builtins
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+    "RunnerConfigurationSchema",
+    "EnvironmentClass",
+    "EnvironmentClassBool",
+    "EnvironmentClassDisplay",
+    "EnvironmentClassEnum",
+    "EnvironmentClassEnumDefaultValue",
+    "EnvironmentClassEnumPossibleValue",
+    "EnvironmentClassInt",
+    "EnvironmentClassString",
+    "RunnerConfig",
+    "RunnerConfigBool",
+    "RunnerConfigDisplay",
+    "RunnerConfigEnum",
+    "RunnerConfigEnumDefaultValue",
+    "RunnerConfigEnumPossibleValue",
+    "RunnerConfigInt",
+    "RunnerConfigString",
+    "Scm",
+    "ScmOAuth",
+    "ScmPat",
+]
+
+
+class EnvironmentClassBool(BaseModel):
+    default: Optional[bool] = None
+
+
+class EnvironmentClassDisplay(BaseModel):
+    default: Optional[str] = None
+
+
+class EnvironmentClassEnumDefaultValue(BaseModel):
+    detail: Optional[str] = None
+
+    subtitle: Optional[str] = None
+
+    title: Optional[str] = None
+
+
+class EnvironmentClassEnumPossibleValue(BaseModel):
+    detail: Optional[str] = None
+
+    subtitle: Optional[str] = None
+
+    title: Optional[str] = None
+
+
+class EnvironmentClassEnum(BaseModel):
+    default: Optional[str] = None
+    """deprecated, will be removed, use default_value instead"""
+
+    default_value: Optional[EnvironmentClassEnumDefaultValue] = FieldInfo(alias="defaultValue", default=None)
+
+    possible_values: Optional[List[EnvironmentClassEnumPossibleValue]] = FieldInfo(alias="possibleValues", default=None)
+
+    values: Optional[List[str]] = None
+    """deprecated, will be removed, use possible_values instead"""
+
+
+class EnvironmentClassInt(BaseModel):
+    default: Optional[int] = None
+
+    max: Optional[int] = None
+
+    min: Optional[int] = None
+
+
+class EnvironmentClassString(BaseModel):
+    default: Optional[str] = None
+
+    pattern: Optional[str] = None
+
+
+class EnvironmentClass(BaseModel):
+    id: Optional[str] = None
+
+    bool: Optional[EnvironmentClassBool] = None
+
+    description: Optional[str] = None
+
+    display: Optional[EnvironmentClassDisplay] = None
+
+    enum: Optional[EnvironmentClassEnum] = None
+
+    int: Optional[EnvironmentClassInt] = None
+
+    name: Optional[str] = None
+
+    required: Optional[builtins.bool] = None
+
+    secret: Optional[builtins.bool] = None
+
+    string: Optional[EnvironmentClassString] = None
+
+
+class RunnerConfigBool(BaseModel):
+    default: Optional[bool] = None
+
+
+class RunnerConfigDisplay(BaseModel):
+    default: Optional[str] = None
+
+
+class RunnerConfigEnumDefaultValue(BaseModel):
+    detail: Optional[str] = None
+
+    subtitle: Optional[str] = None
+
+    title: Optional[str] = None
+
+
+class RunnerConfigEnumPossibleValue(BaseModel):
+    detail: Optional[str] = None
+
+    subtitle: Optional[str] = None
+
+    title: Optional[str] = None
+
+
+class RunnerConfigEnum(BaseModel):
+    default: Optional[str] = None
+    """deprecated, will be removed, use default_value instead"""
+
+    default_value: Optional[RunnerConfigEnumDefaultValue] = FieldInfo(alias="defaultValue", default=None)
+
+    possible_values: Optional[List[RunnerConfigEnumPossibleValue]] = FieldInfo(alias="possibleValues", default=None)
+
+    values: Optional[List[str]] = None
+    """deprecated, will be removed, use possible_values instead"""
+
+
+class RunnerConfigInt(BaseModel):
+    default: Optional[int] = None
+
+    max: Optional[int] = None
+
+    min: Optional[int] = None
+
+
+class RunnerConfigString(BaseModel):
+    default: Optional[str] = None
+
+    pattern: Optional[str] = None
+
+
+class RunnerConfig(BaseModel):
+    id: Optional[str] = None
+
+    bool: Optional[RunnerConfigBool] = None
+
+    description: Optional[str] = None
+
+    display: Optional[RunnerConfigDisplay] = None
+
+    enum: Optional[RunnerConfigEnum] = None
+
+    int: Optional[RunnerConfigInt] = None
+
+    name: Optional[str] = None
+
+    required: Optional[builtins.bool] = None
+
+    secret: Optional[builtins.bool] = None
+
+    string: Optional[RunnerConfigString] = None
+
+
+class ScmOAuth(BaseModel):
+    callback_url: Optional[str] = FieldInfo(alias="callbackUrl", default=None)
+    """
+    callback_url is the URL the OAuth app will redirect to after the user has
+    authenticated.
+    """
+
+
+class ScmPat(BaseModel):
+    description: Optional[str] = None
+    """description is a human-readable description of the PAT."""
+
+    docs_link: Optional[str] = FieldInfo(alias="docsLink", default=None)
+    """
+    docs_link is a link to the documentation on how to create a PAT for this SCM
+    system.
+    """
+
+
+class Scm(BaseModel):
+    default_hosts: Optional[List[str]] = FieldInfo(alias="defaultHosts", default=None)
+
+    name: Optional[str] = None
+
+    oauth: Optional[ScmOAuth] = None
+
+    pat: Optional[ScmPat] = None
+
+    scm_id: Optional[str] = FieldInfo(alias="scmId", default=None)
+
+
+class RunnerConfigurationSchema(BaseModel):
+    environment_classes: Optional[List[EnvironmentClass]] = FieldInfo(alias="environmentClasses", default=None)
+
+    runner_config: Optional[List[RunnerConfig]] = FieldInfo(alias="runnerConfig", default=None)
+
+    scm: Optional[List[Scm]] = None
+
+    version: Optional[str] = None
+    """The schema version"""
diff --git a/src/gitpod/types/runners/configurations/schema_retrieve_params.py b/src/gitpod/types/runners/configurations/schema_retrieve_params.py
new file mode 100644
index 0000000..2988a4a
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/schema_retrieve_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["SchemaRetrieveParams"]
+
+
+class SchemaRetrieveParams(TypedDict, total=False):
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
diff --git a/src/gitpod/types/runners/configurations/schema_retrieve_response.py b/src/gitpod/types/runners/configurations/schema_retrieve_response.py
new file mode 100644
index 0000000..64f13c7
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/schema_retrieve_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from .runner_configuration_schema import RunnerConfigurationSchema
+
+__all__ = ["SchemaRetrieveResponse"]
+
+
+class SchemaRetrieveResponse(BaseModel):
+    schema_: Optional[RunnerConfigurationSchema] = FieldInfo(alias="schema", default=None)
diff --git a/src/gitpod/types/runners/configurations/scm_integration.py b/src/gitpod/types/runners/configurations/scm_integration.py
new file mode 100644
index 0000000..05841dc
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from .scm_integration_oauth_config import ScmIntegrationOAuthConfig
+
+__all__ = ["ScmIntegration"]
+
+
+class ScmIntegration(BaseModel):
+    id: Optional[str] = None
+    """id is the unique identifier of the SCM integration"""
+
+    host: Optional[str] = None
+
+    oauth: Optional[ScmIntegrationOAuthConfig] = None
+
+    pat: Optional[bool] = None
+
+    runner_id: Optional[str] = FieldInfo(alias="runnerId", default=None)
+
+    scm_id: Optional[str] = FieldInfo(alias="scmId", default=None)
+    """
+    scm_id references the scm_id in the runner's configuration schema that this
+    integration is for
+    """
diff --git a/src/gitpod/types/runners/configurations/scm_integration_create_params.py b/src/gitpod/types/runners/configurations/scm_integration_create_params.py
new file mode 100644
index 0000000..8cbddf0
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_create_params.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["ScmIntegrationCreateParams"]
+
+
+class ScmIntegrationCreateParams(TypedDict, total=False):
+    host: str
+
+    issuer_url: Annotated[Optional[str], PropertyInfo(alias="issuerUrl")]
+    """
+    issuer_url can be set to override the authentication provider URL, if it doesn't
+    match the SCM host.
+    """
+
+    oauth_client_id: Annotated[Optional[str], PropertyInfo(alias="oauthClientId")]
+    """
+    oauth_client_id is the OAuth app's client ID, if OAuth is configured. If
+    configured, oauth_plaintext_client_secret must also be set.
+    """
+
+    oauth_plaintext_client_secret: Annotated[Optional[str], PropertyInfo(alias="oauthPlaintextClientSecret")]
+    """
+    oauth_plaintext_client_secret is the OAuth app's client secret in clear text.
+    This will first be encrypted with the runner's public key before being stored.
+    """
+
+    pat: bool
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+
+    scm_id: Annotated[str, PropertyInfo(alias="scmId")]
+    """
+    scm_id references the scm_id in the runner's configuration schema that this
+    integration is for
+    """
diff --git a/src/gitpod/types/runners/configurations/scm_integration_create_response.py b/src/gitpod/types/runners/configurations/scm_integration_create_response.py
new file mode 100644
index 0000000..b71bc66
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_create_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+
+__all__ = ["ScmIntegrationCreateResponse"]
+
+
+class ScmIntegrationCreateResponse(BaseModel):
+    id: Optional[str] = None
+    """id is a uniquely generated identifier for the SCM integration"""
diff --git a/src/gitpod/types/runners/configurations/scm_integration_delete_params.py b/src/gitpod/types/runners/configurations/scm_integration_delete_params.py
new file mode 100644
index 0000000..f2b72fb
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_delete_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ScmIntegrationDeleteParams"]
+
+
+class ScmIntegrationDeleteParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/runners/configurations/scm_integration_list_params.py b/src/gitpod/types/runners/configurations/scm_integration_list_params.py
new file mode 100644
index 0000000..649b18a
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_list_params.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["ScmIntegrationListParams", "Filter", "Pagination"]
+
+
+class ScmIntegrationListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing scm integrations"""
+
+
+class Filter(TypedDict, total=False):
+    runner_ids: Annotated[List[str], PropertyInfo(alias="runnerIds")]
+    """runner_ids filters the response to only SCM integrations of these Runner IDs"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/runners/configurations/scm_integration_oauth_config.py b/src/gitpod/types/runners/configurations/scm_integration_oauth_config.py
new file mode 100644
index 0000000..f113775
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_oauth_config.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["ScmIntegrationOAuthConfig"]
+
+
+class ScmIntegrationOAuthConfig(BaseModel):
+    client_id: Optional[str] = FieldInfo(alias="clientId", default=None)
+    """client_id is the OAuth app's client ID in clear text."""
+
+    encrypted_client_secret: Optional[str] = FieldInfo(alias="encryptedClientSecret", default=None)
+    """
+    encrypted_client_secret is the OAuth app's secret encrypted with the runner's
+    public key.
+    """
+
+    issuer_url: Optional[str] = FieldInfo(alias="issuerUrl", default=None)
+    """
+    issuer_url is used to override the authentication provider URL, if it doesn't
+    match the SCM host.
+
+    +optional if not set, this account is owned by the installation.
+    """
diff --git a/src/gitpod/types/runners/configurations/scm_integration_retrieve_params.py b/src/gitpod/types/runners/configurations/scm_integration_retrieve_params.py
new file mode 100644
index 0000000..d39fe2e
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_retrieve_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ScmIntegrationRetrieveParams"]
+
+
+class ScmIntegrationRetrieveParams(TypedDict, total=False):
+    id: str
diff --git a/src/gitpod/types/runners/configurations/scm_integration_retrieve_response.py b/src/gitpod/types/runners/configurations/scm_integration_retrieve_response.py
new file mode 100644
index 0000000..d19d32d
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_retrieve_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+from .scm_integration import ScmIntegration
+
+__all__ = ["ScmIntegrationRetrieveResponse"]
+
+
+class ScmIntegrationRetrieveResponse(BaseModel):
+    integration: Optional[ScmIntegration] = None
diff --git a/src/gitpod/types/runners/configurations/scm_integration_update_params.py b/src/gitpod/types/runners/configurations/scm_integration_update_params.py
new file mode 100644
index 0000000..706496d
--- /dev/null
+++ b/src/gitpod/types/runners/configurations/scm_integration_update_params.py
@@ -0,0 +1,46 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["ScmIntegrationUpdateParams"]
+
+
+class ScmIntegrationUpdateParams(TypedDict, total=False):
+    id: str
+
+    issuer_url: Annotated[Optional[str], PropertyInfo(alias="issuerUrl")]
+    """
+    issuer_url can be set to override the authentication provider URL, if it doesn't
+    match the SCM host.
+    """
+
+    oauth_client_id: Annotated[Optional[str], PropertyInfo(alias="oauthClientId")]
+    """
+    oauth_client_id can be set to update the OAuth app's client ID. If an empty
+    string is set, the OAuth configuration will be removed (regardless of whether a
+    client secret is set), and any existing Host Authentication Tokens for the SCM
+    integration's runner and host that were created using the OAuth app will be
+    deleted. This might lead to users being unable to access their repositories
+    until they re-authenticate.
+    """
+
+    oauth_plaintext_client_secret: Annotated[Optional[str], PropertyInfo(alias="oauthPlaintextClientSecret")]
+    """
+    oauth_plaintext_client_secret can be set to update the OAuth app's client
+    secret. The cleartext secret will be encrypted with the runner's public key
+    before being stored.
+    """
+
+    pat: Optional[bool]
+    """
+    pat can be set to enable or disable Personal Access Tokens support. When
+    disabling PATs, any existing Host Authentication Tokens for the SCM
+    integration's runner and host that were created using a PAT will be deleted.
+    This might lead to users being unable to access their repositories until they
+    re-authenticate.
+    """
diff --git a/src/gitpod/types/runners/environment_class_validation_result.py b/src/gitpod/types/runners/environment_class_validation_result.py
new file mode 100644
index 0000000..3b7e146
--- /dev/null
+++ b/src/gitpod/types/runners/environment_class_validation_result.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .field_validation_error import FieldValidationError
+
+__all__ = ["EnvironmentClassValidationResult"]
+
+
+class EnvironmentClassValidationResult(BaseModel):
+    configuration_errors: Optional[List[FieldValidationError]] = FieldInfo(alias="configurationErrors", default=None)
+
+    description_error: Optional[str] = FieldInfo(alias="descriptionError", default=None)
+
+    display_name_error: Optional[str] = FieldInfo(alias="displayNameError", default=None)
+
+    valid: Optional[bool] = None
diff --git a/src/gitpod/types/runners/field_validation_error.py b/src/gitpod/types/runners/field_validation_error.py
new file mode 100644
index 0000000..6db05ce
--- /dev/null
+++ b/src/gitpod/types/runners/field_validation_error.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["FieldValidationError"]
+
+
+class FieldValidationError(BaseModel):
+    error: Optional[str] = None
+
+    key: Optional[str] = None
diff --git a/src/gitpod/types/runners/policy_create_params.py b/src/gitpod/types/runners/policy_create_params.py
new file mode 100644
index 0000000..5391627
--- /dev/null
+++ b/src/gitpod/types/runners/policy_create_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .runner_role import RunnerRole
+
+__all__ = ["PolicyCreateParams"]
+
+
+class PolicyCreateParams(TypedDict, total=False):
+    group_id: Annotated[str, PropertyInfo(alias="groupId")]
+    """group_id specifies the group_id identifier"""
+
+    role: RunnerRole
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+    """runner_id specifies the project identifier"""
diff --git a/src/gitpod/types/runners/policy_create_response.py b/src/gitpod/types/runners/policy_create_response.py
new file mode 100644
index 0000000..946e4c8
--- /dev/null
+++ b/src/gitpod/types/runners/policy_create_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .runner_policy import RunnerPolicy
+
+__all__ = ["PolicyCreateResponse"]
+
+
+class PolicyCreateResponse(BaseModel):
+    policy: RunnerPolicy
diff --git a/src/gitpod/types/runners/policy_delete_params.py b/src/gitpod/types/runners/policy_delete_params.py
new file mode 100644
index 0000000..d77deca
--- /dev/null
+++ b/src/gitpod/types/runners/policy_delete_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PolicyDeleteParams"]
+
+
+class PolicyDeleteParams(TypedDict, total=False):
+    group_id: Annotated[str, PropertyInfo(alias="groupId")]
+    """group_id specifies the group_id identifier"""
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+    """runner_id specifies the project identifier"""
diff --git a/src/gitpod/types/runners/policy_list_params.py b/src/gitpod/types/runners/policy_list_params.py
new file mode 100644
index 0000000..8317d42
--- /dev/null
+++ b/src/gitpod/types/runners/policy_list_params.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PolicyListParams", "Pagination"]
+
+
+class PolicyListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing project policies"""
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+    """runner_id specifies the project identifier"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/runners/policy_update_params.py b/src/gitpod/types/runners/policy_update_params.py
new file mode 100644
index 0000000..71066d2
--- /dev/null
+++ b/src/gitpod/types/runners/policy_update_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .runner_role import RunnerRole
+
+__all__ = ["PolicyUpdateParams"]
+
+
+class PolicyUpdateParams(TypedDict, total=False):
+    group_id: Annotated[str, PropertyInfo(alias="groupId")]
+    """group_id specifies the group_id identifier"""
+
+    role: RunnerRole
+
+    runner_id: Annotated[str, PropertyInfo(alias="runnerId")]
+    """runner_id specifies the project identifier"""
diff --git a/src/gitpod/types/runners/policy_update_response.py b/src/gitpod/types/runners/policy_update_response.py
new file mode 100644
index 0000000..7d2094c
--- /dev/null
+++ b/src/gitpod/types/runners/policy_update_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .runner_policy import RunnerPolicy
+
+__all__ = ["PolicyUpdateResponse"]
+
+
+class PolicyUpdateResponse(BaseModel):
+    policy: RunnerPolicy
diff --git a/src/gitpod/types/runners/runner_policy.py b/src/gitpod/types/runners/runner_policy.py
new file mode 100644
index 0000000..c107db4
--- /dev/null
+++ b/src/gitpod/types/runners/runner_policy.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .runner_role import RunnerRole
+
+__all__ = ["RunnerPolicy"]
+
+
+class RunnerPolicy(BaseModel):
+    group_id: Optional[str] = FieldInfo(alias="groupId", default=None)
+
+    role: Optional[RunnerRole] = None
+    """role is the role assigned to the group"""
diff --git a/src/gitpod/types/runners/runner_role.py b/src/gitpod/types/runners/runner_role.py
new file mode 100644
index 0000000..b4e95a1
--- /dev/null
+++ b/src/gitpod/types/runners/runner_role.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["RunnerRole"]
+
+RunnerRole: TypeAlias = Literal["RUNNER_ROLE_UNSPECIFIED", "RUNNER_ROLE_ADMIN", "RUNNER_ROLE_USER"]
diff --git a/src/gitpod/types/runners/scm_integration_validation_result.py b/src/gitpod/types/runners/scm_integration_validation_result.py
new file mode 100644
index 0000000..3a99c80
--- /dev/null
+++ b/src/gitpod/types/runners/scm_integration_validation_result.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ScmIntegrationValidationResult"]
+
+
+class ScmIntegrationValidationResult(BaseModel):
+    host_error: Optional[str] = FieldInfo(alias="hostError", default=None)
+
+    oauth_error: Optional[str] = FieldInfo(alias="oauthError", default=None)
+
+    pat_error: Optional[str] = FieldInfo(alias="patError", default=None)
+
+    scm_id_error: Optional[str] = FieldInfo(alias="scmIdError", default=None)
+
+    valid: Optional[bool] = None
diff --git a/src/gitpod/types/secret.py b/src/gitpod/types/secret.py
new file mode 100644
index 0000000..f7bdd15
--- /dev/null
+++ b/src/gitpod/types/secret.py
@@ -0,0 +1,223 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .secret_scope import SecretScope
+from .shared.subject import Subject
+
+__all__ = ["Secret"]
+
+
+class Secret(BaseModel):
+    id: Optional[str] = None
+
+    container_registry_basic_auth_host: Optional[str] = FieldInfo(alias="containerRegistryBasicAuthHost", default=None)
+    """secret will be mounted as a registry secret"""
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    creator: Optional[Subject] = None
+    """creator is the identity of the creator of the secret"""
+
+    environment_variable: Optional[bool] = FieldInfo(alias="environmentVariable", default=None)
+    """
+    secret will be created as an Environment Variable with the same name as the
+    secret
+    """
+
+    file_path: Optional[str] = FieldInfo(alias="filePath", default=None)
+    """absolute path to the file where the secret is mounted"""
+
+    name: Optional[str] = None
+    """Name of the secret for humans."""
+
+    project_id: Optional[str] = FieldInfo(alias="projectId", default=None)
+    """The Project ID this Secret belongs to Deprecated: use scope instead"""
+
+    scope: Optional[SecretScope] = None
+
+    updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
diff --git a/src/gitpod/types/secret_create_params.py b/src/gitpod/types/secret_create_params.py
new file mode 100644
index 0000000..61487b0
--- /dev/null
+++ b/src/gitpod/types/secret_create_params.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .secret_scope_param import SecretScopeParam
+
+__all__ = ["SecretCreateParams"]
+
+
+class SecretCreateParams(TypedDict, total=False):
+    container_registry_basic_auth_host: Annotated[str, PropertyInfo(alias="containerRegistryBasicAuthHost")]
+    """
+    secret will be mounted as a docker config in the environment VM, mount will have
+    the docker registry host
+    """
+
+    environment_variable: Annotated[bool, PropertyInfo(alias="environmentVariable")]
+    """
+    secret will be created as an Environment Variable with the same name as the
+    secret
+    """
+
+    file_path: Annotated[str, PropertyInfo(alias="filePath")]
+    """
+    absolute path to the file where the secret is mounted value must be an absolute
+    path (start with a /):
+
+    ```
+    this.matches('^/(?:[^/]*/)*.*$')
+    ```
+    """
+
+    name: str
+
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """
+    project_id is the ProjectID this Secret belongs to Deprecated: use scope instead
+    """
+
+    scope: SecretScopeParam
+    """scope is the scope of the secret"""
+
+    value: str
+    """value is the plaintext value of the secret"""
diff --git a/src/gitpod/types/secret_create_response.py b/src/gitpod/types/secret_create_response.py
new file mode 100644
index 0000000..bf7c768
--- /dev/null
+++ b/src/gitpod/types/secret_create_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .secret import Secret
+from .._models import BaseModel
+
+__all__ = ["SecretCreateResponse"]
+
+
+class SecretCreateResponse(BaseModel):
+    secret: Optional[Secret] = None
diff --git a/src/gitpod/types/secret_delete_params.py b/src/gitpod/types/secret_delete_params.py
new file mode 100644
index 0000000..99d8bb6
--- /dev/null
+++ b/src/gitpod/types/secret_delete_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["SecretDeleteParams"]
+
+
+class SecretDeleteParams(TypedDict, total=False):
+    secret_id: Annotated[str, PropertyInfo(alias="secretId")]
diff --git a/src/gitpod/types/secret_get_value_params.py b/src/gitpod/types/secret_get_value_params.py
new file mode 100644
index 0000000..7fbab3d
--- /dev/null
+++ b/src/gitpod/types/secret_get_value_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["SecretGetValueParams"]
+
+
+class SecretGetValueParams(TypedDict, total=False):
+    secret_id: Annotated[str, PropertyInfo(alias="secretId")]
diff --git a/src/gitpod/types/secret_get_value_response.py b/src/gitpod/types/secret_get_value_response.py
new file mode 100644
index 0000000..80a70ac
--- /dev/null
+++ b/src/gitpod/types/secret_get_value_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["SecretGetValueResponse"]
+
+
+class SecretGetValueResponse(BaseModel):
+    value: Optional[str] = None
diff --git a/src/gitpod/types/secret_list_params.py b/src/gitpod/types/secret_list_params.py
new file mode 100644
index 0000000..44ab4bd
--- /dev/null
+++ b/src/gitpod/types/secret_list_params.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .secret_scope_param import SecretScopeParam
+
+__all__ = ["SecretListParams", "Filter", "Pagination"]
+
+
+class SecretListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+    """pagination contains the pagination options for listing environments"""
+
+
+class Filter(TypedDict, total=False):
+    project_ids: Annotated[List[str], PropertyInfo(alias="projectIds")]
+    """
+    project_ids filters the response to only Secrets used by these Project IDs
+    Deprecated: use scope instead. Values in project_ids will be ignored.
+    """
+
+    scope: SecretScopeParam
+    """scope is the scope of the secrets to list"""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/secret_scope.py b/src/gitpod/types/secret_scope.py
new file mode 100644
index 0000000..0c5bc66
--- /dev/null
+++ b/src/gitpod/types/secret_scope.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["SecretScope"]
+
+
+class SecretScope(BaseModel):
+    project_id: Optional[str] = FieldInfo(alias="projectId", default=None)
+    """project_id is the Project ID this Secret belongs to"""
+
+    user_id: Optional[str] = FieldInfo(alias="userId", default=None)
+    """user_id is the User ID this Secret belongs to"""
diff --git a/src/gitpod/types/secret_scope_param.py b/src/gitpod/types/secret_scope_param.py
new file mode 100644
index 0000000..85ee9a4
--- /dev/null
+++ b/src/gitpod/types/secret_scope_param.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["SecretScopeParam"]
+
+
+class SecretScopeParam(TypedDict, total=False):
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """project_id is the Project ID this Secret belongs to"""
+
+    user_id: Annotated[str, PropertyInfo(alias="userId")]
+    """user_id is the User ID this Secret belongs to"""
diff --git a/src/gitpod/types/secret_update_value_params.py b/src/gitpod/types/secret_update_value_params.py
new file mode 100644
index 0000000..0195afc
--- /dev/null
+++ b/src/gitpod/types/secret_update_value_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["SecretUpdateValueParams"]
+
+
+class SecretUpdateValueParams(TypedDict, total=False):
+    secret_id: Annotated[str, PropertyInfo(alias="secretId")]
+
+    value: str
+    """value is the plaintext value of the secret"""
diff --git a/src/gitpod/types/shared/__init__.py b/src/gitpod/types/shared/__init__.py
new file mode 100644
index 0000000..4a5d70a
--- /dev/null
+++ b/src/gitpod/types/shared/__init__.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .task import Task as Task
+from .gateway import Gateway as Gateway
+from .runs_on import RunsOn as RunsOn
+from .subject import Subject as Subject
+from .principal import Principal as Principal
+from .task_spec import TaskSpec as TaskSpec
+from .error_code import ErrorCode as ErrorCode
+from .field_value import FieldValue as FieldValue
+from .user_status import UserStatus as UserStatus
+from .task_metadata import TaskMetadata as TaskMetadata
+from .task_execution import TaskExecution as TaskExecution
+from .environment_class import EnvironmentClass as EnvironmentClass
+from .organization_role import OrganizationRole as OrganizationRole
+from .automation_trigger import AutomationTrigger as AutomationTrigger
+from .task_execution_spec import TaskExecutionSpec as TaskExecutionSpec
+from .task_execution_phase import TaskExecutionPhase as TaskExecutionPhase
+from .task_execution_status import TaskExecutionStatus as TaskExecutionStatus
+from .task_execution_metadata import TaskExecutionMetadata as TaskExecutionMetadata
diff --git a/src/gitpod/types/shared/automation_trigger.py b/src/gitpod/types/shared/automation_trigger.py
new file mode 100644
index 0000000..2b5eba3
--- /dev/null
+++ b/src/gitpod/types/shared/automation_trigger.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["AutomationTrigger"]
+
+
+class AutomationTrigger(BaseModel):
+    manual: Optional[bool] = None
+
+    post_devcontainer_start: Optional[bool] = FieldInfo(alias="postDevcontainerStart", default=None)
+
+    post_environment_start: Optional[bool] = FieldInfo(alias="postEnvironmentStart", default=None)
diff --git a/src/gitpod/types/shared/environment_class.py b/src/gitpod/types/shared/environment_class.py
new file mode 100644
index 0000000..7539415
--- /dev/null
+++ b/src/gitpod/types/shared/environment_class.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .field_value import FieldValue
+
+__all__ = ["EnvironmentClass"]
+
+
+class EnvironmentClass(BaseModel):
+    id: str
+    """id is the unique identifier of the environment class"""
+
+    runner_id: str = FieldInfo(alias="runnerId")
+    """
+    runner_id is the unique identifier of the runner the environment class belongs
+    to
+    """
+
+    configuration: Optional[List[FieldValue]] = None
+    """configuration describes the configuration of the environment class"""
+
+    description: Optional[str] = None
+    """description is a human readable description of the environment class"""
+
+    display_name: Optional[str] = FieldInfo(alias="displayName", default=None)
+    """display_name is the human readable name of the environment class"""
+
+    enabled: Optional[bool] = None
+    """
+    enabled indicates whether the environment class can be used to create new
+    environments.
+    """
diff --git a/src/gitpod/types/shared/error_code.py b/src/gitpod/types/shared/error_code.py
new file mode 100644
index 0000000..316948a
--- /dev/null
+++ b/src/gitpod/types/shared/error_code.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ErrorCode"]
+
+ErrorCode: TypeAlias = Literal[
+    "canceled",
+    "unknown",
+    "invalid_argument",
+    "deadline_exceeded",
+    "not_found",
+    "already_exists",
+    "permission_denied",
+    "resource_exhausted",
+    "failed_precondition",
+    "aborted",
+    "out_of_range",
+    "unimplemented",
+    "internal",
+    "unavailable",
+    "data_loss",
+    "unauthenticated",
+]
diff --git a/src/gitpod/types/shared/field_value.py b/src/gitpod/types/shared/field_value.py
new file mode 100644
index 0000000..90a9019
--- /dev/null
+++ b/src/gitpod/types/shared/field_value.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["FieldValue"]
+
+
+class FieldValue(BaseModel):
+    key: Optional[str] = None
+
+    value: Optional[str] = None
diff --git a/src/gitpod/types/shared/gateway.py b/src/gitpod/types/shared/gateway.py
new file mode 100644
index 0000000..cae2976
--- /dev/null
+++ b/src/gitpod/types/shared/gateway.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["Gateway"]
+
+
+class Gateway(BaseModel):
+    name: str
+    """name is the human-readable name of the gateway.
+
+    name is unique across all gateways.
+    """
+
+    url: str
+    """url of the gateway"""
+
+    region: Optional[str] = None
+    """region is the geographical region where the gateway is located"""
diff --git a/src/gitpod/types/shared/organization_role.py b/src/gitpod/types/shared/organization_role.py
new file mode 100644
index 0000000..202e7a3
--- /dev/null
+++ b/src/gitpod/types/shared/organization_role.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["OrganizationRole"]
+
+OrganizationRole: TypeAlias = Literal[
+    "ORGANIZATION_ROLE_UNSPECIFIED", "ORGANIZATION_ROLE_ADMIN", "ORGANIZATION_ROLE_MEMBER"
+]
diff --git a/src/gitpod/types/shared/principal.py b/src/gitpod/types/shared/principal.py
new file mode 100644
index 0000000..5300b52
--- /dev/null
+++ b/src/gitpod/types/shared/principal.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["Principal"]
+
+Principal: TypeAlias = Literal[
+    "PRINCIPAL_UNSPECIFIED",
+    "PRINCIPAL_ACCOUNT",
+    "PRINCIPAL_USER",
+    "PRINCIPAL_RUNNER",
+    "PRINCIPAL_ENVIRONMENT",
+    "PRINCIPAL_SERVICE_ACCOUNT",
+    "PRINCIPAL_RUNNER_MANAGER",
+]
diff --git a/src/gitpod/types/shared/runs_on.py b/src/gitpod/types/shared/runs_on.py
new file mode 100644
index 0000000..7702279
--- /dev/null
+++ b/src/gitpod/types/shared/runs_on.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["RunsOn", "Docker"]
+
+
+class Docker(BaseModel):
+    environment: Optional[List[str]] = None
+
+    image: Optional[str] = None
+
+
+class RunsOn(BaseModel):
+    docker: Docker
diff --git a/src/gitpod/types/shared/subject.py b/src/gitpod/types/shared/subject.py
new file mode 100644
index 0000000..88a258c
--- /dev/null
+++ b/src/gitpod/types/shared/subject.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .principal import Principal
+
+__all__ = ["Subject"]
+
+
+class Subject(BaseModel):
+    id: Optional[str] = None
+    """id is the UUID of the subject"""
+
+    principal: Optional[Principal] = None
+    """Principal is the principal of the subject"""
diff --git a/src/gitpod/types/shared/task.py b/src/gitpod/types/shared/task.py
new file mode 100644
index 0000000..c45bfa6
--- /dev/null
+++ b/src/gitpod/types/shared/task.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .task_spec import TaskSpec
+from .task_metadata import TaskMetadata
+
+__all__ = ["Task"]
+
+
+class Task(BaseModel):
+    id: str
+
+    depends_on: Optional[List[str]] = FieldInfo(alias="dependsOn", default=None)
+    """dependencies specifies the IDs of the automations this task depends on."""
+
+    environment_id: Optional[str] = FieldInfo(alias="environmentId", default=None)
+
+    metadata: Optional[TaskMetadata] = None
+
+    spec: Optional[TaskSpec] = None
diff --git a/src/gitpod/types/shared/task_execution.py b/src/gitpod/types/shared/task_execution.py
new file mode 100644
index 0000000..3274176
--- /dev/null
+++ b/src/gitpod/types/shared/task_execution.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .task_execution_spec import TaskExecutionSpec
+from .task_execution_status import TaskExecutionStatus
+from .task_execution_metadata import TaskExecutionMetadata
+
+__all__ = ["TaskExecution"]
+
+
+class TaskExecution(BaseModel):
+    id: str
+
+    metadata: Optional[TaskExecutionMetadata] = None
+
+    spec: Optional[TaskExecutionSpec] = None
+
+    status: Optional[TaskExecutionStatus] = None
diff --git a/src/gitpod/types/shared/task_execution_metadata.py b/src/gitpod/types/shared/task_execution_metadata.py
new file mode 100644
index 0000000..8e72321
--- /dev/null
+++ b/src/gitpod/types/shared/task_execution_metadata.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .subject import Subject
+from ..._models import BaseModel
+
+__all__ = ["TaskExecutionMetadata"]
+
+
+class TaskExecutionMetadata(BaseModel):
+    completed_at: Optional[datetime] = FieldInfo(alias="completedAt", default=None)
+    """completed_at is the time the task execution was done."""
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """created_at is the time the task was created."""
+
+    creator: Optional[Subject] = None
+    """creator describes the principal who created/started the task run."""
+
+    environment_id: Optional[str] = FieldInfo(alias="environmentId", default=None)
+    """environment_id is the ID of the environment in which the task run is executed."""
+
+    started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None)
+    """started_at is the time the task execution actually started to run."""
+
+    started_by: Optional[str] = FieldInfo(alias="startedBy", default=None)
+    """started_by describes the trigger that started the task execution."""
+
+    task_id: Optional[str] = FieldInfo(alias="taskId", default=None)
+    """task_id is the ID of the main task being executed."""
diff --git a/src/gitpod/types/shared/task_execution_phase.py b/src/gitpod/types/shared/task_execution_phase.py
new file mode 100644
index 0000000..84b6f31
--- /dev/null
+++ b/src/gitpod/types/shared/task_execution_phase.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["TaskExecutionPhase"]
+
+TaskExecutionPhase: TypeAlias = Literal[
+    "TASK_EXECUTION_PHASE_UNSPECIFIED",
+    "TASK_EXECUTION_PHASE_PENDING",
+    "TASK_EXECUTION_PHASE_RUNNING",
+    "TASK_EXECUTION_PHASE_SUCCEEDED",
+    "TASK_EXECUTION_PHASE_FAILED",
+    "TASK_EXECUTION_PHASE_STOPPED",
+]
diff --git a/src/gitpod/types/shared/task_execution_spec.py b/src/gitpod/types/shared/task_execution_spec.py
new file mode 100644
index 0000000..17a4bff
--- /dev/null
+++ b/src/gitpod/types/shared/task_execution_spec.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .task_spec import TaskSpec
+from .task_execution_phase import TaskExecutionPhase
+
+__all__ = ["TaskExecutionSpec", "Plan", "PlanStep", "PlanStepTask"]
+
+
+class PlanStepTask(BaseModel):
+    id: Optional[str] = None
+
+    spec: Optional[TaskSpec] = None
+
+
+class PlanStep(BaseModel):
+    id: Optional[str] = None
+    """ID is the ID of the execution step"""
+
+    depends_on: Optional[List[str]] = FieldInfo(alias="dependsOn", default=None)
+
+    label: Optional[str] = None
+
+    service_id: Optional[str] = FieldInfo(alias="serviceId", default=None)
+
+    task: Optional[PlanStepTask] = None
+
+
+class Plan(BaseModel):
+    steps: Optional[List[PlanStep]] = None
+
+
+class TaskExecutionSpec(BaseModel):
+    desired_phase: Optional[TaskExecutionPhase] = FieldInfo(alias="desiredPhase", default=None)
+    """desired_phase is the phase the task execution should be in.
+
+    Used to stop a running task execution early.
+    """
+
+    plan: Optional[List[Plan]] = None
+    """plan is a list of groups of steps.
+
+    The steps in a group are executed concurrently, while the groups are executed
+    sequentially. The order of the groups is the order in which they are executed.
+    """
diff --git a/src/gitpod/types/shared/task_execution_status.py b/src/gitpod/types/shared/task_execution_status.py
new file mode 100644
index 0000000..2a4765a
--- /dev/null
+++ b/src/gitpod/types/shared/task_execution_status.py
@@ -0,0 +1,64 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .task_execution_phase import TaskExecutionPhase
+
+__all__ = ["TaskExecutionStatus", "Step"]
+
+
+class Step(BaseModel):
+    id: Optional[str] = None
+    """ID is the ID of the execution step"""
+
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message summarises why the step failed to operate.
+
+    If this is non-empty the step has failed to operate and will likely transition
+    to a failed state.
+    """
+
+    output: Optional[Dict[str, str]] = None
+    """
+    output contains the output of the task execution. setting an output field to
+    empty string will unset it.
+    """
+
+    phase: Optional[TaskExecutionPhase] = None
+    """phase is the current phase of the execution step"""
+
+
+class TaskExecutionStatus(BaseModel):
+    failure_message: Optional[str] = FieldInfo(alias="failureMessage", default=None)
+    """failure_message summarises why the task execution failed to operate.
+
+    If this is non-empty the task execution has failed to operate and will likely
+    transition to a failed state.
+    """
+
+    log_url: Optional[str] = FieldInfo(alias="logUrl", default=None)
+    """log_url is the URL to the logs of the task's steps.
+
+    If this is empty, the task either has no logs or has not yet started.
+    """
+
+    phase: Optional[TaskExecutionPhase] = None
+    """the phase of a task execution represents the aggregated phase of all steps."""
+
+    status_version: Optional[str] = FieldInfo(alias="statusVersion", default=None)
+    """version of the status update.
+
+    Task executions themselves are unversioned, but their status has different
+    versions. The value of this field has no semantic meaning (e.g. don't interpret
+    it as as a timestamp), but it can be used to impose a partial order. If
+    a.status_version < b.status_version then a was the status before b.
+    """
+
+    steps: Optional[List[Step]] = None
+    """steps provides the status for each individual step of the task execution.
+
+    If a step is missing it has not yet started.
+    """
diff --git a/src/gitpod/types/shared/task_metadata.py b/src/gitpod/types/shared/task_metadata.py
new file mode 100644
index 0000000..6a34633
--- /dev/null
+++ b/src/gitpod/types/shared/task_metadata.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .subject import Subject
+from ..._models import BaseModel
+from .automation_trigger import AutomationTrigger
+
+__all__ = ["TaskMetadata"]
+
+
+class TaskMetadata(BaseModel):
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """created_at is the time the task was created."""
+
+    creator: Optional[Subject] = None
+    """creator describes the principal who created the task."""
+
+    description: Optional[str] = None
+    """description is a user-facing description for the task.
+
+    It can be used to provide context and documentation for the task.
+    """
+
+    name: Optional[str] = None
+    """name is a user-facing name for the task.
+
+    Unlike the reference, this field is not unique, and not referenced by the
+    system. This is a short descriptive name for the task.
+    """
+
+    reference: Optional[str] = None
+    """
+    reference is a user-facing identifier for the task which must be unique on the
+    environment. It is used to express dependencies between tasks, and to identify
+    the task in user interactions (e.g. the CLI).
+    """
+
+    triggered_by: Optional[List[AutomationTrigger]] = FieldInfo(alias="triggeredBy", default=None)
+    """triggered_by is a list of trigger that start the task."""
diff --git a/src/gitpod/types/shared/task_spec.py b/src/gitpod/types/shared/task_spec.py
new file mode 100644
index 0000000..c69815f
--- /dev/null
+++ b/src/gitpod/types/shared/task_spec.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .runs_on import RunsOn
+from ..._models import BaseModel
+
+__all__ = ["TaskSpec"]
+
+
+class TaskSpec(BaseModel):
+    command: Optional[str] = None
+    """command contains the command the task should execute"""
+
+    runs_on: Optional[RunsOn] = FieldInfo(alias="runsOn", default=None)
+    """runs_on specifies the environment the task should run on."""
diff --git a/src/gitpod/types/shared/user_status.py b/src/gitpod/types/shared/user_status.py
new file mode 100644
index 0000000..d13e58c
--- /dev/null
+++ b/src/gitpod/types/shared/user_status.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["UserStatus"]
+
+UserStatus: TypeAlias = Literal[
+    "USER_STATUS_UNSPECIFIED", "USER_STATUS_ACTIVE", "USER_STATUS_SUSPENDED", "USER_STATUS_LEFT"
+]
diff --git a/src/gitpod/types/shared_params/__init__.py b/src/gitpod/types/shared_params/__init__.py
new file mode 100644
index 0000000..51915e7
--- /dev/null
+++ b/src/gitpod/types/shared_params/__init__.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .runs_on import RunsOn as RunsOn
+from .subject import Subject as Subject
+from .principal import Principal as Principal
+from .task_spec import TaskSpec as TaskSpec
+from .field_value import FieldValue as FieldValue
+from .task_metadata import TaskMetadata as TaskMetadata
+from .environment_class import EnvironmentClass as EnvironmentClass
+from .organization_role import OrganizationRole as OrganizationRole
+from .automation_trigger import AutomationTrigger as AutomationTrigger
+from .task_execution_phase import TaskExecutionPhase as TaskExecutionPhase
diff --git a/src/gitpod/types/shared_params/automation_trigger.py b/src/gitpod/types/shared_params/automation_trigger.py
new file mode 100644
index 0000000..173dff4
--- /dev/null
+++ b/src/gitpod/types/shared_params/automation_trigger.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["AutomationTrigger"]
+
+
+class AutomationTrigger(TypedDict, total=False):
+    manual: bool
+
+    post_devcontainer_start: Annotated[bool, PropertyInfo(alias="postDevcontainerStart")]
+
+    post_environment_start: Annotated[bool, PropertyInfo(alias="postEnvironmentStart")]
diff --git a/src/gitpod/types/shared_params/environment_class.py b/src/gitpod/types/shared_params/environment_class.py
new file mode 100644
index 0000000..b310694
--- /dev/null
+++ b/src/gitpod/types/shared_params/environment_class.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .field_value import FieldValue
+
+__all__ = ["EnvironmentClass"]
+
+
+class EnvironmentClass(TypedDict, total=False):
+    id: Required[str]
+    """id is the unique identifier of the environment class"""
+
+    runner_id: Required[Annotated[str, PropertyInfo(alias="runnerId")]]
+    """
+    runner_id is the unique identifier of the runner the environment class belongs
+    to
+    """
+
+    configuration: Iterable[FieldValue]
+    """configuration describes the configuration of the environment class"""
+
+    description: str
+    """description is a human readable description of the environment class"""
+
+    display_name: Annotated[str, PropertyInfo(alias="displayName")]
+    """display_name is the human readable name of the environment class"""
+
+    enabled: bool
+    """
+    enabled indicates whether the environment class can be used to create new
+    environments.
+    """
diff --git a/src/gitpod/types/shared_params/field_value.py b/src/gitpod/types/shared_params/field_value.py
new file mode 100644
index 0000000..05f28c1
--- /dev/null
+++ b/src/gitpod/types/shared_params/field_value.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["FieldValue"]
+
+
+class FieldValue(TypedDict, total=False):
+    key: str
+
+    value: str
diff --git a/src/gitpod/types/shared_params/organization_role.py b/src/gitpod/types/shared_params/organization_role.py
new file mode 100644
index 0000000..1c3d4f1
--- /dev/null
+++ b/src/gitpod/types/shared_params/organization_role.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["OrganizationRole"]
+
+OrganizationRole: TypeAlias = Literal[
+    "ORGANIZATION_ROLE_UNSPECIFIED", "ORGANIZATION_ROLE_ADMIN", "ORGANIZATION_ROLE_MEMBER"
+]
diff --git a/src/gitpod/types/shared_params/principal.py b/src/gitpod/types/shared_params/principal.py
new file mode 100644
index 0000000..3cefcfa
--- /dev/null
+++ b/src/gitpod/types/shared_params/principal.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["Principal"]
+
+Principal: TypeAlias = Literal[
+    "PRINCIPAL_UNSPECIFIED",
+    "PRINCIPAL_ACCOUNT",
+    "PRINCIPAL_USER",
+    "PRINCIPAL_RUNNER",
+    "PRINCIPAL_ENVIRONMENT",
+    "PRINCIPAL_SERVICE_ACCOUNT",
+    "PRINCIPAL_RUNNER_MANAGER",
+]
diff --git a/src/gitpod/types/shared_params/runs_on.py b/src/gitpod/types/shared_params/runs_on.py
new file mode 100644
index 0000000..55a079f
--- /dev/null
+++ b/src/gitpod/types/shared_params/runs_on.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Required, TypedDict
+
+__all__ = ["RunsOn", "Docker"]
+
+
+class Docker(TypedDict, total=False):
+    environment: List[str]
+
+    image: str
+
+
+class RunsOn(TypedDict, total=False):
+    docker: Required[Docker]
diff --git a/src/gitpod/types/shared_params/subject.py b/src/gitpod/types/shared_params/subject.py
new file mode 100644
index 0000000..1c1293b
--- /dev/null
+++ b/src/gitpod/types/shared_params/subject.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from ..shared.principal import Principal
+
+__all__ = ["Subject"]
+
+
+class Subject(TypedDict, total=False):
+    id: str
+    """id is the UUID of the subject"""
+
+    principal: Principal
+    """Principal is the principal of the subject"""
diff --git a/src/gitpod/types/shared_params/task_execution_phase.py b/src/gitpod/types/shared_params/task_execution_phase.py
new file mode 100644
index 0000000..60b1bc3
--- /dev/null
+++ b/src/gitpod/types/shared_params/task_execution_phase.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["TaskExecutionPhase"]
+
+TaskExecutionPhase: TypeAlias = Literal[
+    "TASK_EXECUTION_PHASE_UNSPECIFIED",
+    "TASK_EXECUTION_PHASE_PENDING",
+    "TASK_EXECUTION_PHASE_RUNNING",
+    "TASK_EXECUTION_PHASE_SUCCEEDED",
+    "TASK_EXECUTION_PHASE_FAILED",
+    "TASK_EXECUTION_PHASE_STOPPED",
+]
diff --git a/src/gitpod/types/shared_params/task_metadata.py b/src/gitpod/types/shared_params/task_metadata.py
new file mode 100644
index 0000000..2523ba6
--- /dev/null
+++ b/src/gitpod/types/shared_params/task_metadata.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable
+from datetime import datetime
+from typing_extensions import Annotated, TypedDict
+
+from .subject import Subject
+from ..._utils import PropertyInfo
+from .automation_trigger import AutomationTrigger
+
+__all__ = ["TaskMetadata"]
+
+
+class TaskMetadata(TypedDict, total=False):
+    created_at: Annotated[Union[str, datetime], PropertyInfo(alias="createdAt", format="iso8601")]
+    """created_at is the time the task was created."""
+
+    creator: Subject
+    """creator describes the principal who created the task."""
+
+    description: str
+    """description is a user-facing description for the task.
+
+    It can be used to provide context and documentation for the task.
+    """
+
+    name: str
+    """name is a user-facing name for the task.
+
+    Unlike the reference, this field is not unique, and not referenced by the
+    system. This is a short descriptive name for the task.
+    """
+
+    reference: str
+    """
+    reference is a user-facing identifier for the task which must be unique on the
+    environment. It is used to express dependencies between tasks, and to identify
+    the task in user interactions (e.g. the CLI).
+    """
+
+    triggered_by: Annotated[Iterable[AutomationTrigger], PropertyInfo(alias="triggeredBy")]
+    """triggered_by is a list of trigger that start the task."""
diff --git a/src/gitpod/types/shared_params/task_spec.py b/src/gitpod/types/shared_params/task_spec.py
new file mode 100644
index 0000000..5f40eef
--- /dev/null
+++ b/src/gitpod/types/shared_params/task_spec.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .runs_on import RunsOn
+from ..._utils import PropertyInfo
+
+__all__ = ["TaskSpec"]
+
+
+class TaskSpec(TypedDict, total=False):
+    command: str
+    """command contains the command the task should execute"""
+
+    runs_on: Annotated[RunsOn, PropertyInfo(alias="runsOn")]
+    """runs_on specifies the environment the task should run on."""
diff --git a/src/gitpod/types/usage_list_environment_runtime_records_params.py b/src/gitpod/types/usage_list_environment_runtime_records_params.py
new file mode 100644
index 0000000..3f97243
--- /dev/null
+++ b/src/gitpod/types/usage_list_environment_runtime_records_params.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["UsageListEnvironmentRuntimeRecordsParams", "Filter", "FilterDateRange", "Pagination"]
+
+
+class UsageListEnvironmentRuntimeRecordsParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+    """Filter options."""
+
+    pagination: Pagination
+    """Pagination options."""
+
+
+class FilterDateRange(TypedDict, total=False):
+    end_time: Required[Annotated[Union[str, datetime], PropertyInfo(alias="endTime", format="iso8601")]]
+    """End time of the date range (exclusive)."""
+
+    start_time: Required[Annotated[Union[str, datetime], PropertyInfo(alias="startTime", format="iso8601")]]
+    """Start time of the date range (inclusive)."""
+
+
+class Filter(TypedDict, total=False):
+    date_range: Required[Annotated[FilterDateRange, PropertyInfo(alias="dateRange")]]
+    """Date range to query runtime records within."""
+
+    project_id: Annotated[str, PropertyInfo(alias="projectId")]
+    """Optional project ID to filter runtime records by."""
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/user.py b/src/gitpod/types/user.py
new file mode 100644
index 0000000..a8ce63f
--- /dev/null
+++ b/src/gitpod/types/user.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.user_status import UserStatus
+
+__all__ = ["User"]
+
+
+class User(BaseModel):
+    id: str
+    """id is a UUID of the user"""
+
+    avatar_url: Optional[str] = FieldInfo(alias="avatarUrl", default=None)
+    """avatar_url is a link to the user avatar"""
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """created_at is the creation time"""
+
+    name: Optional[str] = None
+    """name is the full name of the user"""
+
+    organization_id: Optional[str] = FieldInfo(alias="organizationId", default=None)
+    """organization_id is the id of the organization this account is owned by.
+
+    +optional if not set, this account is owned by the installation.
+    """
+
+    status: Optional[UserStatus] = None
+    """status is the status the user is in"""
diff --git a/src/gitpod/types/user_get_authenticated_user_params.py b/src/gitpod/types/user_get_authenticated_user_params.py
new file mode 100644
index 0000000..2b65cbb
--- /dev/null
+++ b/src/gitpod/types/user_get_authenticated_user_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["UserGetAuthenticatedUserParams"]
+
+
+class UserGetAuthenticatedUserParams(TypedDict, total=False):
+    empty: bool
diff --git a/src/gitpod/types/user_get_authenticated_user_response.py b/src/gitpod/types/user_get_authenticated_user_response.py
new file mode 100644
index 0000000..8610b45
--- /dev/null
+++ b/src/gitpod/types/user_get_authenticated_user_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .user import User
+from .._models import BaseModel
+
+__all__ = ["UserGetAuthenticatedUserResponse"]
+
+
+class UserGetAuthenticatedUserResponse(BaseModel):
+    user: User
diff --git a/src/gitpod/types/user_set_suspended_params.py b/src/gitpod/types/user_set_suspended_params.py
new file mode 100644
index 0000000..1a4e3d9
--- /dev/null
+++ b/src/gitpod/types/user_set_suspended_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["UserSetSuspendedParams"]
+
+
+class UserSetSuspendedParams(TypedDict, total=False):
+    suspended: bool
+
+    user_id: Annotated[str, PropertyInfo(alias="userId")]
diff --git a/src/gitpod/types/users/__init__.py b/src/gitpod/types/users/__init__.py
new file mode 100644
index 0000000..8145891
--- /dev/null
+++ b/src/gitpod/types/users/__init__.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .pat_get_params import PatGetParams as PatGetParams
+from .pat_list_params import PatListParams as PatListParams
+from .pat_get_response import PatGetResponse as PatGetResponse
+from .pat_delete_params import PatDeleteParams as PatDeleteParams
+from .dotfile_get_params import DotfileGetParams as DotfileGetParams
+from .dotfile_set_params import DotfileSetParams as DotfileSetParams
+from .dotfile_get_response import DotfileGetResponse as DotfileGetResponse
+from .personal_access_token import PersonalAccessToken as PersonalAccessToken
+from .dotfiles_configuration import DotfilesConfiguration as DotfilesConfiguration
diff --git a/src/gitpod/types/users/dotfile_get_params.py b/src/gitpod/types/users/dotfile_get_params.py
new file mode 100644
index 0000000..f9272c3
--- /dev/null
+++ b/src/gitpod/types/users/dotfile_get_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["DotfileGetParams"]
+
+
+class DotfileGetParams(TypedDict, total=False):
+    empty: bool
diff --git a/src/gitpod/types/users/dotfile_get_response.py b/src/gitpod/types/users/dotfile_get_response.py
new file mode 100644
index 0000000..4cb5e02
--- /dev/null
+++ b/src/gitpod/types/users/dotfile_get_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .dotfiles_configuration import DotfilesConfiguration
+
+__all__ = ["DotfileGetResponse"]
+
+
+class DotfileGetResponse(BaseModel):
+    dotfiles_configuration: DotfilesConfiguration = FieldInfo(alias="dotfilesConfiguration")
diff --git a/src/gitpod/types/users/dotfile_set_params.py b/src/gitpod/types/users/dotfile_set_params.py
new file mode 100644
index 0000000..21d63bd
--- /dev/null
+++ b/src/gitpod/types/users/dotfile_set_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["DotfileSetParams"]
+
+
+class DotfileSetParams(TypedDict, total=False):
+    repository: str
diff --git a/src/gitpod/types/users/dotfiles_configuration.py b/src/gitpod/types/users/dotfiles_configuration.py
new file mode 100644
index 0000000..c5595be
--- /dev/null
+++ b/src/gitpod/types/users/dotfiles_configuration.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["DotfilesConfiguration"]
+
+
+class DotfilesConfiguration(BaseModel):
+    repository: Optional[str] = None
+    """The URL of a dotfiles repository."""
diff --git a/src/gitpod/types/users/pat_delete_params.py b/src/gitpod/types/users/pat_delete_params.py
new file mode 100644
index 0000000..990e660
--- /dev/null
+++ b/src/gitpod/types/users/pat_delete_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PatDeleteParams"]
+
+
+class PatDeleteParams(TypedDict, total=False):
+    personal_access_token_id: Annotated[str, PropertyInfo(alias="personalAccessTokenId")]
diff --git a/src/gitpod/types/users/pat_get_params.py b/src/gitpod/types/users/pat_get_params.py
new file mode 100644
index 0000000..6851c57
--- /dev/null
+++ b/src/gitpod/types/users/pat_get_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PatGetParams"]
+
+
+class PatGetParams(TypedDict, total=False):
+    personal_access_token_id: Annotated[str, PropertyInfo(alias="personalAccessTokenId")]
diff --git a/src/gitpod/types/users/pat_get_response.py b/src/gitpod/types/users/pat_get_response.py
new file mode 100644
index 0000000..5c0b2a0
--- /dev/null
+++ b/src/gitpod/types/users/pat_get_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .personal_access_token import PersonalAccessToken
+
+__all__ = ["PatGetResponse"]
+
+
+class PatGetResponse(BaseModel):
+    pat: PersonalAccessToken
diff --git a/src/gitpod/types/users/pat_list_params.py b/src/gitpod/types/users/pat_list_params.py
new file mode 100644
index 0000000..b03f6d6
--- /dev/null
+++ b/src/gitpod/types/users/pat_list_params.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PatListParams", "Filter", "Pagination"]
+
+
+class PatListParams(TypedDict, total=False):
+    token: str
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+
+    filter: Filter
+
+    pagination: Pagination
+
+
+class Filter(TypedDict, total=False):
+    user_ids: Annotated[List[str], PropertyInfo(alias="userIds")]
+    """
+    creator_ids filters the response to only Environments created by specified
+    members
+    """
+
+
+class Pagination(TypedDict, total=False):
+    token: str
+    """
+    Token for the next set of results that was returned as next_token of a
+    PaginationResponse
+    """
+
+    page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+    """Page size is the maximum number of results to retrieve per page. Defaults to 25.
+
+    Maximum 100.
+    """
diff --git a/src/gitpod/types/users/personal_access_token.py b/src/gitpod/types/users/personal_access_token.py
new file mode 100644
index 0000000..c094c3c
--- /dev/null
+++ b/src/gitpod/types/users/personal_access_token.py
@@ -0,0 +1,297 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.subject import Subject
+
+__all__ = ["PersonalAccessToken"]
+
+
+class PersonalAccessToken(BaseModel):
+    id: Optional[str] = None
+
+    created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    creator: Optional[Subject] = None
+
+    description: Optional[str] = None
+
+    expires_at: Optional[datetime] = FieldInfo(alias="expiresAt", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    last_used: Optional[datetime] = FieldInfo(alias="lastUsed", default=None)
+    """
+    A Timestamp represents a point in time independent of any time zone or local
+    calendar, encoded as a count of seconds and fractions of seconds at nanosecond
+    resolution. The count is relative to an epoch at UTC midnight on January 1,
+    1970, in the proleptic Gregorian calendar which extends the Gregorian calendar
+    backwards to year one.
+
+    All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+    second table is needed for interpretation, using a
+    [24-hour linear smear](https://developers.google.com/time/smear).
+
+    The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+    restricting to that range, we ensure that we can convert to and from
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+
+    # Examples
+
+    Example 1: Compute Timestamp from POSIX `time()`.
+
+         Timestamp timestamp;
+         timestamp.set_seconds(time(NULL));
+         timestamp.set_nanos(0);
+
+    Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+
+         struct timeval tv;
+         gettimeofday(&tv, NULL);
+
+         Timestamp timestamp;
+         timestamp.set_seconds(tv.tv_sec);
+         timestamp.set_nanos(tv.tv_usec * 1000);
+
+    Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+
+         FILETIME ft;
+         GetSystemTimeAsFileTime(&ft);
+         UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+         // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+         // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+         Timestamp timestamp;
+         timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+         timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+
+    Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+
+         long millis = System.currentTimeMillis();
+
+         Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+             .setNanos((int) ((millis % 1000) * 1000000)).build();
+
+    Example 5: Compute Timestamp from Java `Instant.now()`.
+
+         Instant now = Instant.now();
+
+         Timestamp timestamp =
+             Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+                 .setNanos(now.getNano()).build();
+
+    Example 6: Compute Timestamp from current time in Python.
+
+         timestamp = Timestamp()
+         timestamp.GetCurrentTime()
+
+    # JSON Mapping
+
+    In JSON format, the Timestamp type is encoded as a string in the
+    [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is
+    "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is always
+    expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are
+    zero-padded to two digits each. The fractional seconds, which can go up to 9
+    digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix
+    indicates the timezone ("UTC"); the timezone is required. A proto3 JSON
+    serializer should always use UTC (as indicated by "Z") when printing the
+    Timestamp type and a proto3 JSON parser should be able to accept both UTC and
+    other timezones (as indicated by an offset).
+
+    For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on
+    January 15, 2017.
+
+    In JavaScript, one can convert a Date object to this format using the standard
+    [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+    method. In Python, a standard `datetime.datetime` object can be converted to
+    this format using
+    [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with the
+    time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the
+    Joda Time's
+    [`ISODateTimeFormat.dateTime()`](<http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()>)
+    to obtain a formatter capable of generating timestamps in this format.
+    """
+
+    user_id: Optional[str] = FieldInfo(alias="userId", default=None)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/__init__.py b/tests/api_resources/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/environments/__init__.py b/tests/api_resources/environments/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/environments/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/environments/automations/__init__.py b/tests/api_resources/environments/automations/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/environments/automations/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/environments/automations/tasks/__init__.py b/tests/api_resources/environments/automations/tasks/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/environments/automations/tasks/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/environments/automations/tasks/test_executions.py b/tests/api_resources/environments/automations/tasks/test_executions.py
new file mode 100644
index 0000000..aa72d5f
--- /dev/null
+++ b/tests/api_resources/environments/automations/tasks/test_executions.py
@@ -0,0 +1,268 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncTaskExecutionsPage, AsyncTaskExecutionsPage
+from gitpod.types.shared import TaskExecution
+from gitpod.types.environments.automations.tasks import (
+    ExecutionRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestExecutions:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        execution = client.environments.automations.tasks.executions.retrieve()
+        assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        execution = client.environments.automations.tasks.executions.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.executions.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        execution = response.parse()
+        assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.executions.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            execution = response.parse()
+            assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        execution = client.environments.automations.tasks.executions.list()
+        assert_matches_type(SyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        execution = client.environments.automations.tasks.executions.list(
+            token="token",
+            page_size=0,
+            filter={
+                "environment_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "phases": ["TASK_EXECUTION_PHASE_RUNNING", "TASK_EXECUTION_PHASE_FAILED"],
+                "task_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "task_references": ["string"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.executions.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        execution = response.parse()
+        assert_matches_type(SyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.executions.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            execution = response.parse()
+            assert_matches_type(SyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_stop(self, client: Gitpod) -> None:
+        execution = client.environments.automations.tasks.executions.stop()
+        assert_matches_type(object, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_stop_with_all_params(self, client: Gitpod) -> None:
+        execution = client.environments.automations.tasks.executions.stop(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_stop(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.executions.with_raw_response.stop()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        execution = response.parse()
+        assert_matches_type(object, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_stop(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.executions.with_streaming_response.stop() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            execution = response.parse()
+            assert_matches_type(object, execution, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncExecutions:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        execution = await async_client.environments.automations.tasks.executions.retrieve()
+        assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        execution = await async_client.environments.automations.tasks.executions.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.executions.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        execution = await response.parse()
+        assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with (
+            async_client.environments.automations.tasks.executions.with_streaming_response.retrieve()
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            execution = await response.parse()
+            assert_matches_type(ExecutionRetrieveResponse, execution, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        execution = await async_client.environments.automations.tasks.executions.list()
+        assert_matches_type(AsyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        execution = await async_client.environments.automations.tasks.executions.list(
+            token="token",
+            page_size=0,
+            filter={
+                "environment_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "phases": ["TASK_EXECUTION_PHASE_RUNNING", "TASK_EXECUTION_PHASE_FAILED"],
+                "task_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "task_references": ["string"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.executions.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        execution = await response.parse()
+        assert_matches_type(AsyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.executions.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            execution = await response.parse()
+            assert_matches_type(AsyncTaskExecutionsPage[TaskExecution], execution, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_stop(self, async_client: AsyncGitpod) -> None:
+        execution = await async_client.environments.automations.tasks.executions.stop()
+        assert_matches_type(object, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_stop_with_all_params(self, async_client: AsyncGitpod) -> None:
+        execution = await async_client.environments.automations.tasks.executions.stop(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_stop(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.executions.with_raw_response.stop()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        execution = await response.parse()
+        assert_matches_type(object, execution, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_stop(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.executions.with_streaming_response.stop() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            execution = await response.parse()
+            assert_matches_type(object, execution, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/environments/automations/test_services.py b/tests/api_resources/environments/automations/test_services.py
new file mode 100644
index 0000000..64db132
--- /dev/null
+++ b/tests/api_resources/environments/automations/test_services.py
@@ -0,0 +1,688 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod._utils import parse_datetime
+from gitpod.pagination import SyncServicesPage, AsyncServicesPage
+from gitpod.types.environments.automations import (
+    Service,
+    ServiceCreateResponse,
+    ServiceRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestServices:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.create()
+        assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.create(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            metadata={
+                "created_at": parse_datetime("2019-12-27T18:11:19.117Z"),
+                "creator": {
+                    "id": "id",
+                    "principal": "PRINCIPAL_UNSPECIFIED",
+                },
+                "description": "Runs the development web server",
+                "name": "Web Server",
+                "reference": "web-server",
+                "triggered_by": [
+                    {
+                        "manual": True,
+                        "post_devcontainer_start": True,
+                        "post_environment_start": True,
+                    }
+                ],
+            },
+            spec={
+                "commands": {
+                    "ready": "curl -s http://localhost:3000",
+                    "start": "npm run dev",
+                    "stop": "stop",
+                },
+                "desired_phase": "SERVICE_PHASE_UNSPECIFIED",
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+                "session": "session",
+                "spec_version": "specVersion",
+            },
+        )
+        assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.environments.automations.services.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = response.parse()
+        assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.environments.automations.services.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = response.parse()
+            assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.retrieve()
+        assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.environments.automations.services.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = response.parse()
+        assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.environments.automations.services.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = response.parse()
+            assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.update()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            metadata={
+                "description": "description",
+                "name": "x",
+                "triggered_by": {
+                    "trigger": [
+                        {
+                            "manual": True,
+                            "post_devcontainer_start": True,
+                            "post_environment_start": True,
+                        }
+                    ]
+                },
+            },
+            spec={
+                "commands": {
+                    "ready": "curl -s http://localhost:8080",
+                    "start": "npm run start:dev",
+                    "stop": "stop",
+                },
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+            },
+            status={
+                "failure_message": "failureMessage",
+                "log_url": "logUrl",
+                "output": {"foo": "string"},
+                "phase": "SERVICE_PHASE_UNSPECIFIED",
+                "session": "session",
+            },
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.environments.automations.services.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.environments.automations.services.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.list()
+        assert_matches_type(SyncServicesPage[Service], service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.list(
+            token="token",
+            page_size=0,
+            filter={
+                "environment_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "references": ["web-server", "database"],
+                "service_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncServicesPage[Service], service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.environments.automations.services.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = response.parse()
+        assert_matches_type(SyncServicesPage[Service], service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.environments.automations.services.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = response.parse()
+            assert_matches_type(SyncServicesPage[Service], service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.delete()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            force=False,
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.environments.automations.services.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.environments.automations.services.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_start(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.start()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_start_with_all_params(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.start(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_start(self, client: Gitpod) -> None:
+        response = client.environments.automations.services.with_raw_response.start()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_start(self, client: Gitpod) -> None:
+        with client.environments.automations.services.with_streaming_response.start() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_stop(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.stop()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_stop_with_all_params(self, client: Gitpod) -> None:
+        service = client.environments.automations.services.stop(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_stop(self, client: Gitpod) -> None:
+        response = client.environments.automations.services.with_raw_response.stop()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_stop(self, client: Gitpod) -> None:
+        with client.environments.automations.services.with_streaming_response.stop() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncServices:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.create()
+        assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.create(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            metadata={
+                "created_at": parse_datetime("2019-12-27T18:11:19.117Z"),
+                "creator": {
+                    "id": "id",
+                    "principal": "PRINCIPAL_UNSPECIFIED",
+                },
+                "description": "Runs the development web server",
+                "name": "Web Server",
+                "reference": "web-server",
+                "triggered_by": [
+                    {
+                        "manual": True,
+                        "post_devcontainer_start": True,
+                        "post_environment_start": True,
+                    }
+                ],
+            },
+            spec={
+                "commands": {
+                    "ready": "curl -s http://localhost:3000",
+                    "start": "npm run dev",
+                    "stop": "stop",
+                },
+                "desired_phase": "SERVICE_PHASE_UNSPECIFIED",
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+                "session": "session",
+                "spec_version": "specVersion",
+            },
+        )
+        assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.services.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = await response.parse()
+        assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.services.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = await response.parse()
+            assert_matches_type(ServiceCreateResponse, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.retrieve()
+        assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.services.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = await response.parse()
+        assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.services.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = await response.parse()
+            assert_matches_type(ServiceRetrieveResponse, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.update()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            metadata={
+                "description": "description",
+                "name": "x",
+                "triggered_by": {
+                    "trigger": [
+                        {
+                            "manual": True,
+                            "post_devcontainer_start": True,
+                            "post_environment_start": True,
+                        }
+                    ]
+                },
+            },
+            spec={
+                "commands": {
+                    "ready": "curl -s http://localhost:8080",
+                    "start": "npm run start:dev",
+                    "stop": "stop",
+                },
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+            },
+            status={
+                "failure_message": "failureMessage",
+                "log_url": "logUrl",
+                "output": {"foo": "string"},
+                "phase": "SERVICE_PHASE_UNSPECIFIED",
+                "session": "session",
+            },
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.services.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = await response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.services.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = await response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.list()
+        assert_matches_type(AsyncServicesPage[Service], service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.list(
+            token="token",
+            page_size=0,
+            filter={
+                "environment_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "references": ["web-server", "database"],
+                "service_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncServicesPage[Service], service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.services.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = await response.parse()
+        assert_matches_type(AsyncServicesPage[Service], service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.services.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = await response.parse()
+            assert_matches_type(AsyncServicesPage[Service], service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.delete()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            force=False,
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.services.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = await response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.services.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = await response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_start(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.start()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_start_with_all_params(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.start(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_start(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.services.with_raw_response.start()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = await response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_start(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.services.with_streaming_response.start() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = await response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_stop(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.stop()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_stop_with_all_params(self, async_client: AsyncGitpod) -> None:
+        service = await async_client.environments.automations.services.stop(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_stop(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.services.with_raw_response.stop()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        service = await response.parse()
+        assert_matches_type(object, service, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_stop(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.services.with_streaming_response.stop() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            service = await response.parse()
+            assert_matches_type(object, service, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/environments/automations/test_tasks.py b/tests/api_resources/environments/automations/test_tasks.py
new file mode 100644
index 0000000..3d97263
--- /dev/null
+++ b/tests/api_resources/environments/automations/test_tasks.py
@@ -0,0 +1,583 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod._utils import parse_datetime
+from gitpod.pagination import SyncTasksPage, AsyncTasksPage
+from gitpod.types.shared import Task
+from gitpod.types.environments.automations import (
+    TaskStartResponse,
+    TaskCreateResponse,
+    TaskRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestTasks:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.create()
+        assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.create(
+            depends_on=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            metadata={
+                "created_at": parse_datetime("2019-12-27T18:11:19.117Z"),
+                "creator": {
+                    "id": "id",
+                    "principal": "PRINCIPAL_UNSPECIFIED",
+                },
+                "description": "Builds the project artifacts",
+                "name": "Build Project",
+                "reference": "build",
+                "triggered_by": [
+                    {
+                        "manual": True,
+                        "post_devcontainer_start": True,
+                        "post_environment_start": True,
+                    }
+                ],
+            },
+            spec={
+                "command": "npm run build",
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+            },
+        )
+        assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = response.parse()
+        assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = response.parse()
+            assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.retrieve()
+        assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = response.parse()
+        assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = response.parse()
+            assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.update()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            depends_on=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            metadata={
+                "description": "description",
+                "name": "x",
+                "triggered_by": {
+                    "trigger": [
+                        {
+                            "manual": True,
+                            "post_devcontainer_start": True,
+                            "post_environment_start": True,
+                        }
+                    ]
+                },
+            },
+            spec={
+                "command": "npm run test:coverage",
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+            },
+        )
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = response.parse()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = response.parse()
+            assert_matches_type(object, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.list()
+        assert_matches_type(SyncTasksPage[Task], task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.list(
+            token="token",
+            page_size=0,
+            filter={
+                "environment_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "references": ["build", "test"],
+                "task_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncTasksPage[Task], task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = response.parse()
+        assert_matches_type(SyncTasksPage[Task], task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = response.parse()
+            assert_matches_type(SyncTasksPage[Task], task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.delete()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = response.parse()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = response.parse()
+            assert_matches_type(object, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_start(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.start()
+        assert_matches_type(TaskStartResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_start_with_all_params(self, client: Gitpod) -> None:
+        task = client.environments.automations.tasks.start(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(TaskStartResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_start(self, client: Gitpod) -> None:
+        response = client.environments.automations.tasks.with_raw_response.start()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = response.parse()
+        assert_matches_type(TaskStartResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_start(self, client: Gitpod) -> None:
+        with client.environments.automations.tasks.with_streaming_response.start() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = response.parse()
+            assert_matches_type(TaskStartResponse, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncTasks:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.create()
+        assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.create(
+            depends_on=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            metadata={
+                "created_at": parse_datetime("2019-12-27T18:11:19.117Z"),
+                "creator": {
+                    "id": "id",
+                    "principal": "PRINCIPAL_UNSPECIFIED",
+                },
+                "description": "Builds the project artifacts",
+                "name": "Build Project",
+                "reference": "build",
+                "triggered_by": [
+                    {
+                        "manual": True,
+                        "post_devcontainer_start": True,
+                        "post_environment_start": True,
+                    }
+                ],
+            },
+            spec={
+                "command": "npm run build",
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+            },
+        )
+        assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = await response.parse()
+        assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = await response.parse()
+            assert_matches_type(TaskCreateResponse, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.retrieve()
+        assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = await response.parse()
+        assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = await response.parse()
+            assert_matches_type(TaskRetrieveResponse, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.update()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            depends_on=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            metadata={
+                "description": "description",
+                "name": "x",
+                "triggered_by": {
+                    "trigger": [
+                        {
+                            "manual": True,
+                            "post_devcontainer_start": True,
+                            "post_environment_start": True,
+                        }
+                    ]
+                },
+            },
+            spec={
+                "command": "npm run test:coverage",
+                "runs_on": {
+                    "docker": {
+                        "environment": ["string"],
+                        "image": "x",
+                    }
+                },
+            },
+        )
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = await response.parse()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = await response.parse()
+            assert_matches_type(object, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.list()
+        assert_matches_type(AsyncTasksPage[Task], task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.list(
+            token="token",
+            page_size=0,
+            filter={
+                "environment_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "references": ["build", "test"],
+                "task_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncTasksPage[Task], task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = await response.parse()
+        assert_matches_type(AsyncTasksPage[Task], task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = await response.parse()
+            assert_matches_type(AsyncTasksPage[Task], task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.delete()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = await response.parse()
+        assert_matches_type(object, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = await response.parse()
+            assert_matches_type(object, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_start(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.start()
+        assert_matches_type(TaskStartResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_start_with_all_params(self, async_client: AsyncGitpod) -> None:
+        task = await async_client.environments.automations.tasks.start(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(TaskStartResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_start(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.tasks.with_raw_response.start()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        task = await response.parse()
+        assert_matches_type(TaskStartResponse, task, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_start(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.tasks.with_streaming_response.start() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            task = await response.parse()
+            assert_matches_type(TaskStartResponse, task, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/environments/test_automations.py b/tests/api_resources/environments/test_automations.py
new file mode 100644
index 0000000..40362d6
--- /dev/null
+++ b/tests/api_resources/environments/test_automations.py
@@ -0,0 +1,166 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types.environments import AutomationUpsertResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAutomations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_upsert(self, client: Gitpod) -> None:
+        automation = client.environments.automations.upsert()
+        assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_upsert_with_all_params(self, client: Gitpod) -> None:
+        automation = client.environments.automations.upsert(
+            automations_file={
+                "services": {
+                    "web-server": {
+                        "commands": {
+                            "ready": "curl -s http://localhost:3000",
+                            "start": "npm run dev",
+                            "stop": "stop",
+                        },
+                        "description": "Development web server",
+                        "name": "Web Server",
+                        "runs_on": {
+                            "docker": {
+                                "environment": ["string"],
+                                "image": "x",
+                            }
+                        },
+                        "triggered_by": ["postDevcontainerStart"],
+                    }
+                },
+                "tasks": {
+                    "build": {
+                        "command": "npm run build",
+                        "depends_on": ["string"],
+                        "description": "Builds the project artifacts",
+                        "name": "Build Project",
+                        "runs_on": {
+                            "docker": {
+                                "environment": ["string"],
+                                "image": "x",
+                            }
+                        },
+                        "triggered_by": ["postEnvironmentStart"],
+                    }
+                },
+            },
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_upsert(self, client: Gitpod) -> None:
+        response = client.environments.automations.with_raw_response.upsert()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        automation = response.parse()
+        assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_upsert(self, client: Gitpod) -> None:
+        with client.environments.automations.with_streaming_response.upsert() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            automation = response.parse()
+            assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncAutomations:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_upsert(self, async_client: AsyncGitpod) -> None:
+        automation = await async_client.environments.automations.upsert()
+        assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_upsert_with_all_params(self, async_client: AsyncGitpod) -> None:
+        automation = await async_client.environments.automations.upsert(
+            automations_file={
+                "services": {
+                    "web-server": {
+                        "commands": {
+                            "ready": "curl -s http://localhost:3000",
+                            "start": "npm run dev",
+                            "stop": "stop",
+                        },
+                        "description": "Development web server",
+                        "name": "Web Server",
+                        "runs_on": {
+                            "docker": {
+                                "environment": ["string"],
+                                "image": "x",
+                            }
+                        },
+                        "triggered_by": ["postDevcontainerStart"],
+                    }
+                },
+                "tasks": {
+                    "build": {
+                        "command": "npm run build",
+                        "depends_on": ["string"],
+                        "description": "Builds the project artifacts",
+                        "name": "Build Project",
+                        "runs_on": {
+                            "docker": {
+                                "environment": ["string"],
+                                "image": "x",
+                            }
+                        },
+                        "triggered_by": ["postEnvironmentStart"],
+                    }
+                },
+            },
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_upsert(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.automations.with_raw_response.upsert()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        automation = await response.parse()
+        assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_upsert(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.automations.with_streaming_response.upsert() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            automation = await response.parse()
+            assert_matches_type(AutomationUpsertResponse, automation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/environments/test_classes.py b/tests/api_resources/environments/test_classes.py
new file mode 100644
index 0000000..3049c13
--- /dev/null
+++ b/tests/api_resources/environments/test_classes.py
@@ -0,0 +1,121 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncEnvironmentClassesPage, AsyncEnvironmentClassesPage
+from gitpod.types.shared import EnvironmentClass
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestClasses:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        class_ = client.environments.classes.list()
+        assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        class_ = client.environments.classes.list(
+            token="token",
+            page_size=0,
+            filter={
+                "can_create_environments": True,
+                "enabled": True,
+                "runner_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "runner_kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "runner_providers": ["RUNNER_PROVIDER_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.environments.classes.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        class_ = response.parse()
+        assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.environments.classes.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            class_ = response.parse()
+            assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncClasses:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        class_ = await async_client.environments.classes.list()
+        assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        class_ = await async_client.environments.classes.list(
+            token="token",
+            page_size=0,
+            filter={
+                "can_create_environments": True,
+                "enabled": True,
+                "runner_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "runner_kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "runner_providers": ["RUNNER_PROVIDER_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.classes.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        class_ = await response.parse()
+        assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.classes.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            class_ = await response.parse()
+            assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], class_, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/organizations/__init__.py b/tests/api_resources/organizations/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/organizations/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/organizations/test_domain_verifications.py b/tests/api_resources/organizations/test_domain_verifications.py
new file mode 100644
index 0000000..b9e06da
--- /dev/null
+++ b/tests/api_resources/organizations/test_domain_verifications.py
@@ -0,0 +1,406 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncDomainVerificationsPage, AsyncDomainVerificationsPage
+from gitpod.types.organizations import (
+    DomainVerification,
+    DomainVerificationCreateResponse,
+    DomainVerificationVerifyResponse,
+    DomainVerificationRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestDomainVerifications:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        domain_verification = client.organizations.domain_verifications.create(
+            domain="acme-corp.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(DomainVerificationCreateResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.organizations.domain_verifications.with_raw_response.create(
+            domain="acme-corp.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = response.parse()
+        assert_matches_type(DomainVerificationCreateResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.organizations.domain_verifications.with_streaming_response.create(
+            domain="acme-corp.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = response.parse()
+            assert_matches_type(DomainVerificationCreateResponse, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        domain_verification = client.organizations.domain_verifications.retrieve(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(DomainVerificationRetrieveResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.organizations.domain_verifications.with_raw_response.retrieve(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = response.parse()
+        assert_matches_type(DomainVerificationRetrieveResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.organizations.domain_verifications.with_streaming_response.retrieve(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = response.parse()
+            assert_matches_type(DomainVerificationRetrieveResponse, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        domain_verification = client.organizations.domain_verifications.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(SyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        domain_verification = client.organizations.domain_verifications.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.organizations.domain_verifications.with_raw_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = response.parse()
+        assert_matches_type(SyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.organizations.domain_verifications.with_streaming_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = response.parse()
+            assert_matches_type(SyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        domain_verification = client.organizations.domain_verifications.delete(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.organizations.domain_verifications.with_raw_response.delete(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = response.parse()
+        assert_matches_type(object, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.organizations.domain_verifications.with_streaming_response.delete(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = response.parse()
+            assert_matches_type(object, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_verify(self, client: Gitpod) -> None:
+        domain_verification = client.organizations.domain_verifications.verify(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(DomainVerificationVerifyResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_verify(self, client: Gitpod) -> None:
+        response = client.organizations.domain_verifications.with_raw_response.verify(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = response.parse()
+        assert_matches_type(DomainVerificationVerifyResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_verify(self, client: Gitpod) -> None:
+        with client.organizations.domain_verifications.with_streaming_response.verify(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = response.parse()
+            assert_matches_type(DomainVerificationVerifyResponse, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncDomainVerifications:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        domain_verification = await async_client.organizations.domain_verifications.create(
+            domain="acme-corp.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(DomainVerificationCreateResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.domain_verifications.with_raw_response.create(
+            domain="acme-corp.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = await response.parse()
+        assert_matches_type(DomainVerificationCreateResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.domain_verifications.with_streaming_response.create(
+            domain="acme-corp.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = await response.parse()
+            assert_matches_type(DomainVerificationCreateResponse, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        domain_verification = await async_client.organizations.domain_verifications.retrieve(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(DomainVerificationRetrieveResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.domain_verifications.with_raw_response.retrieve(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = await response.parse()
+        assert_matches_type(DomainVerificationRetrieveResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.domain_verifications.with_streaming_response.retrieve(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = await response.parse()
+            assert_matches_type(DomainVerificationRetrieveResponse, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        domain_verification = await async_client.organizations.domain_verifications.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(AsyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        domain_verification = await async_client.organizations.domain_verifications.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.domain_verifications.with_raw_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = await response.parse()
+        assert_matches_type(AsyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.domain_verifications.with_streaming_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = await response.parse()
+            assert_matches_type(
+                AsyncDomainVerificationsPage[DomainVerification], domain_verification, path=["response"]
+            )
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        domain_verification = await async_client.organizations.domain_verifications.delete(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.domain_verifications.with_raw_response.delete(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = await response.parse()
+        assert_matches_type(object, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.domain_verifications.with_streaming_response.delete(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = await response.parse()
+            assert_matches_type(object, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_verify(self, async_client: AsyncGitpod) -> None:
+        domain_verification = await async_client.organizations.domain_verifications.verify(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(DomainVerificationVerifyResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_verify(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.domain_verifications.with_raw_response.verify(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        domain_verification = await response.parse()
+        assert_matches_type(DomainVerificationVerifyResponse, domain_verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_verify(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.domain_verifications.with_streaming_response.verify(
+            domain_verification_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            domain_verification = await response.parse()
+            assert_matches_type(DomainVerificationVerifyResponse, domain_verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/organizations/test_invites.py b/tests/api_resources/organizations/test_invites.py
new file mode 100644
index 0000000..d1cc059
--- /dev/null
+++ b/tests/api_resources/organizations/test_invites.py
@@ -0,0 +1,232 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types.organizations import (
+    InviteCreateResponse,
+    InviteRetrieveResponse,
+    InviteGetSummaryResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestInvites:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        invite = client.organizations.invites.create(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(InviteCreateResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.organizations.invites.with_raw_response.create(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        invite = response.parse()
+        assert_matches_type(InviteCreateResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.organizations.invites.with_streaming_response.create(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            invite = response.parse()
+            assert_matches_type(InviteCreateResponse, invite, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        invite = client.organizations.invites.retrieve(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(InviteRetrieveResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.organizations.invites.with_raw_response.retrieve(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        invite = response.parse()
+        assert_matches_type(InviteRetrieveResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.organizations.invites.with_streaming_response.retrieve(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            invite = response.parse()
+            assert_matches_type(InviteRetrieveResponse, invite, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_summary(self, client: Gitpod) -> None:
+        invite = client.organizations.invites.get_summary(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(InviteGetSummaryResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get_summary(self, client: Gitpod) -> None:
+        response = client.organizations.invites.with_raw_response.get_summary(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        invite = response.parse()
+        assert_matches_type(InviteGetSummaryResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get_summary(self, client: Gitpod) -> None:
+        with client.organizations.invites.with_streaming_response.get_summary(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            invite = response.parse()
+            assert_matches_type(InviteGetSummaryResponse, invite, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncInvites:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        invite = await async_client.organizations.invites.create(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(InviteCreateResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.invites.with_raw_response.create(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        invite = await response.parse()
+        assert_matches_type(InviteCreateResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.invites.with_streaming_response.create(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            invite = await response.parse()
+            assert_matches_type(InviteCreateResponse, invite, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        invite = await async_client.organizations.invites.retrieve(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(InviteRetrieveResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.invites.with_raw_response.retrieve(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        invite = await response.parse()
+        assert_matches_type(InviteRetrieveResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.invites.with_streaming_response.retrieve(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            invite = await response.parse()
+            assert_matches_type(InviteRetrieveResponse, invite, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_summary(self, async_client: AsyncGitpod) -> None:
+        invite = await async_client.organizations.invites.get_summary(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(InviteGetSummaryResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get_summary(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.invites.with_raw_response.get_summary(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        invite = await response.parse()
+        assert_matches_type(InviteGetSummaryResponse, invite, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get_summary(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.invites.with_streaming_response.get_summary(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            invite = await response.parse()
+            assert_matches_type(InviteGetSummaryResponse, invite, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/organizations/test_policies.py b/tests/api_resources/organizations/test_policies.py
new file mode 100644
index 0000000..b539b9c
--- /dev/null
+++ b/tests/api_resources/organizations/test_policies.py
@@ -0,0 +1,196 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types.organizations import PolicyRetrieveResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPolicies:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        policy = client.organizations.policies.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(PolicyRetrieveResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.organizations.policies.with_raw_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(PolicyRetrieveResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.organizations.policies.with_streaming_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(PolicyRetrieveResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        policy = client.organizations.policies.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        policy = client.organizations.policies.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            allowed_editor_ids=["string"],
+            allow_local_runners=True,
+            default_editor_id="defaultEditorId",
+            default_environment_image="defaultEnvironmentImage",
+            maximum_environments_per_user="20",
+            maximum_environment_timeout="3600s",
+            maximum_running_environments_per_user="5",
+            members_create_projects=True,
+            members_require_projects=True,
+            port_sharing_disabled=True,
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.organizations.policies.with_raw_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.organizations.policies.with_streaming_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(object, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncPolicies:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.organizations.policies.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(PolicyRetrieveResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.policies.with_raw_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(PolicyRetrieveResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.policies.with_streaming_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(PolicyRetrieveResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.organizations.policies.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.organizations.policies.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            allowed_editor_ids=["string"],
+            allow_local_runners=True,
+            default_editor_id="defaultEditorId",
+            default_environment_image="defaultEnvironmentImage",
+            maximum_environments_per_user="20",
+            maximum_environment_timeout="3600s",
+            maximum_running_environments_per_user="5",
+            members_create_projects=True,
+            members_require_projects=True,
+            port_sharing_disabled=True,
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.policies.with_raw_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.policies.with_streaming_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(object, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/organizations/test_sso_configurations.py b/tests/api_resources/organizations/test_sso_configurations.py
new file mode 100644
index 0000000..deba284
--- /dev/null
+++ b/tests/api_resources/organizations/test_sso_configurations.py
@@ -0,0 +1,449 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncSSOConfigurationsPage, AsyncSSOConfigurationsPage
+from gitpod.types.organizations import (
+    SSOConfiguration,
+    SSOConfigurationCreateResponse,
+    SSOConfigurationRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSSOConfigurations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        sso_configuration = client.organizations.sso_configurations.create(
+            client_id="012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com",
+            client_secret="GOCSPX-abcdefghijklmnopqrstuvwxyz123456",
+            email_domain="acme-corp.com",
+            issuer_url="https://accounts.google.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(SSOConfigurationCreateResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.organizations.sso_configurations.with_raw_response.create(
+            client_id="012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com",
+            client_secret="GOCSPX-abcdefghijklmnopqrstuvwxyz123456",
+            email_domain="acme-corp.com",
+            issuer_url="https://accounts.google.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = response.parse()
+        assert_matches_type(SSOConfigurationCreateResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.organizations.sso_configurations.with_streaming_response.create(
+            client_id="012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com",
+            client_secret="GOCSPX-abcdefghijklmnopqrstuvwxyz123456",
+            email_domain="acme-corp.com",
+            issuer_url="https://accounts.google.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = response.parse()
+            assert_matches_type(SSOConfigurationCreateResponse, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        sso_configuration = client.organizations.sso_configurations.retrieve(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(SSOConfigurationRetrieveResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.organizations.sso_configurations.with_raw_response.retrieve(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = response.parse()
+        assert_matches_type(SSOConfigurationRetrieveResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.organizations.sso_configurations.with_streaming_response.retrieve(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = response.parse()
+            assert_matches_type(SSOConfigurationRetrieveResponse, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        sso_configuration = client.organizations.sso_configurations.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        sso_configuration = client.organizations.sso_configurations.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            claims={"foo": "string"},
+            client_id="new-client-id",
+            client_secret="new-client-secret",
+            email_domain="xxxx",
+            issuer_url="https://example.com",
+            state="SSO_CONFIGURATION_STATE_UNSPECIFIED",
+        )
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.organizations.sso_configurations.with_raw_response.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = response.parse()
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.organizations.sso_configurations.with_streaming_response.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = response.parse()
+            assert_matches_type(object, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        sso_configuration = client.organizations.sso_configurations.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(SyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        sso_configuration = client.organizations.sso_configurations.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.organizations.sso_configurations.with_raw_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = response.parse()
+        assert_matches_type(SyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.organizations.sso_configurations.with_streaming_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = response.parse()
+            assert_matches_type(SyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        sso_configuration = client.organizations.sso_configurations.delete(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.organizations.sso_configurations.with_raw_response.delete(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = response.parse()
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.organizations.sso_configurations.with_streaming_response.delete(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = response.parse()
+            assert_matches_type(object, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSSOConfigurations:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        sso_configuration = await async_client.organizations.sso_configurations.create(
+            client_id="012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com",
+            client_secret="GOCSPX-abcdefghijklmnopqrstuvwxyz123456",
+            email_domain="acme-corp.com",
+            issuer_url="https://accounts.google.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(SSOConfigurationCreateResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.sso_configurations.with_raw_response.create(
+            client_id="012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com",
+            client_secret="GOCSPX-abcdefghijklmnopqrstuvwxyz123456",
+            email_domain="acme-corp.com",
+            issuer_url="https://accounts.google.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = await response.parse()
+        assert_matches_type(SSOConfigurationCreateResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.sso_configurations.with_streaming_response.create(
+            client_id="012345678-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com",
+            client_secret="GOCSPX-abcdefghijklmnopqrstuvwxyz123456",
+            email_domain="acme-corp.com",
+            issuer_url="https://accounts.google.com",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = await response.parse()
+            assert_matches_type(SSOConfigurationCreateResponse, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        sso_configuration = await async_client.organizations.sso_configurations.retrieve(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(SSOConfigurationRetrieveResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.sso_configurations.with_raw_response.retrieve(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = await response.parse()
+        assert_matches_type(SSOConfigurationRetrieveResponse, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.sso_configurations.with_streaming_response.retrieve(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = await response.parse()
+            assert_matches_type(SSOConfigurationRetrieveResponse, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        sso_configuration = await async_client.organizations.sso_configurations.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        sso_configuration = await async_client.organizations.sso_configurations.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            claims={"foo": "string"},
+            client_id="new-client-id",
+            client_secret="new-client-secret",
+            email_domain="xxxx",
+            issuer_url="https://example.com",
+            state="SSO_CONFIGURATION_STATE_UNSPECIFIED",
+        )
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.sso_configurations.with_raw_response.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = await response.parse()
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.sso_configurations.with_streaming_response.update(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = await response.parse()
+            assert_matches_type(object, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        sso_configuration = await async_client.organizations.sso_configurations.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(AsyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        sso_configuration = await async_client.organizations.sso_configurations.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.sso_configurations.with_raw_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = await response.parse()
+        assert_matches_type(AsyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.sso_configurations.with_streaming_response.list(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = await response.parse()
+            assert_matches_type(AsyncSSOConfigurationsPage[SSOConfiguration], sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        sso_configuration = await async_client.organizations.sso_configurations.delete(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.sso_configurations.with_raw_response.delete(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        sso_configuration = await response.parse()
+        assert_matches_type(object, sso_configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.sso_configurations.with_streaming_response.delete(
+            sso_configuration_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            sso_configuration = await response.parse()
+            assert_matches_type(object, sso_configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/projects/__init__.py b/tests/api_resources/projects/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/projects/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/projects/test_policies.py b/tests/api_resources/projects/test_policies.py
new file mode 100644
index 0000000..430b982
--- /dev/null
+++ b/tests/api_resources/projects/test_policies.py
@@ -0,0 +1,339 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncPoliciesPage, AsyncPoliciesPage
+from gitpod.types.projects import (
+    ProjectPolicy,
+    PolicyCreateResponse,
+    PolicyUpdateResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPolicies:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        policy = client.projects.policies.create()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        policy = client.projects.policies.create(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            role="PROJECT_ROLE_ADMIN",
+        )
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.projects.policies.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.projects.policies.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        policy = client.projects.policies.update()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        policy = client.projects.policies.update(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            role="PROJECT_ROLE_EDITOR",
+        )
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.projects.policies.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.projects.policies.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        policy = client.projects.policies.list()
+        assert_matches_type(SyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        policy = client.projects.policies.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(SyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.projects.policies.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(SyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.projects.policies.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(SyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        policy = client.projects.policies.delete()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        policy = client.projects.policies.delete(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.projects.policies.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.projects.policies.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(object, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncPolicies:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.create()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.create(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            role="PROJECT_ROLE_ADMIN",
+        )
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.policies.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.policies.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.update()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.update(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            role="PROJECT_ROLE_EDITOR",
+        )
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.policies.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.policies.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.list()
+        assert_matches_type(AsyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(AsyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.policies.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(AsyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.policies.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(AsyncPoliciesPage[ProjectPolicy], policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.delete()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.projects.policies.delete(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.policies.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.policies.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(object, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/runners/__init__.py b/tests/api_resources/runners/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/runners/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/runners/configurations/__init__.py b/tests/api_resources/runners/configurations/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/runners/configurations/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/runners/configurations/test_environment_classes.py b/tests/api_resources/runners/configurations/test_environment_classes.py
new file mode 100644
index 0000000..649e807
--- /dev/null
+++ b/tests/api_resources/runners/configurations/test_environment_classes.py
@@ -0,0 +1,373 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncEnvironmentClassesPage, AsyncEnvironmentClassesPage
+from gitpod.types.shared import EnvironmentClass
+from gitpod.types.runners.configurations import (
+    EnvironmentClassCreateResponse,
+    EnvironmentClassRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEnvironmentClasses:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.create()
+        assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.create(
+            configuration=[
+                {
+                    "key": "cpu",
+                    "value": "8",
+                },
+                {
+                    "key": "memory",
+                    "value": "16384",
+                },
+            ],
+            description="8 CPU, 16GB RAM",
+            display_name="Large Instance",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.runners.configurations.environment_classes.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = response.parse()
+        assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.runners.configurations.environment_classes.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = response.parse()
+            assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.retrieve()
+        assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.retrieve(
+            environment_class_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.runners.configurations.environment_classes.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = response.parse()
+        assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.runners.configurations.environment_classes.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = response.parse()
+            assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.update()
+        assert_matches_type(object, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.update(
+            description="16 CPU, 32GB RAM",
+            display_name="Updated Large Instance",
+            enabled=True,
+            environment_class_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.runners.configurations.environment_classes.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = response.parse()
+        assert_matches_type(object, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.runners.configurations.environment_classes.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = response.parse()
+            assert_matches_type(object, environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.list()
+        assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        environment_class = client.runners.configurations.environment_classes.list(
+            token="token",
+            page_size=0,
+            filter={
+                "can_create_environments": True,
+                "enabled": True,
+                "runner_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "runner_kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "runner_providers": ["RUNNER_PROVIDER_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.runners.configurations.environment_classes.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = response.parse()
+        assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.runners.configurations.environment_classes.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = response.parse()
+            assert_matches_type(SyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncEnvironmentClasses:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.create()
+        assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.create(
+            configuration=[
+                {
+                    "key": "cpu",
+                    "value": "8",
+                },
+                {
+                    "key": "memory",
+                    "value": "16384",
+                },
+            ],
+            description="8 CPU, 16GB RAM",
+            display_name="Large Instance",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.environment_classes.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = await response.parse()
+        assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.environment_classes.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = await response.parse()
+            assert_matches_type(EnvironmentClassCreateResponse, environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.retrieve()
+        assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.retrieve(
+            environment_class_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.environment_classes.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = await response.parse()
+        assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with (
+            async_client.runners.configurations.environment_classes.with_streaming_response.retrieve()
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = await response.parse()
+            assert_matches_type(EnvironmentClassRetrieveResponse, environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.update()
+        assert_matches_type(object, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.update(
+            description="16 CPU, 32GB RAM",
+            display_name="Updated Large Instance",
+            enabled=True,
+            environment_class_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.environment_classes.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = await response.parse()
+        assert_matches_type(object, environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.environment_classes.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = await response.parse()
+            assert_matches_type(object, environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.list()
+        assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment_class = await async_client.runners.configurations.environment_classes.list(
+            token="token",
+            page_size=0,
+            filter={
+                "can_create_environments": True,
+                "enabled": True,
+                "runner_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "runner_kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "runner_providers": ["RUNNER_PROVIDER_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.environment_classes.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment_class = await response.parse()
+        assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.environment_classes.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment_class = await response.parse()
+            assert_matches_type(AsyncEnvironmentClassesPage[EnvironmentClass], environment_class, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/runners/configurations/test_host_authentication_tokens.py b/tests/api_resources/runners/configurations/test_host_authentication_tokens.py
new file mode 100644
index 0000000..0766e3a
--- /dev/null
+++ b/tests/api_resources/runners/configurations/test_host_authentication_tokens.py
@@ -0,0 +1,436 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod._utils import parse_datetime
+from gitpod.pagination import SyncTokensPage, AsyncTokensPage
+from gitpod.types.runners.configurations import (
+    HostAuthenticationToken,
+    HostAuthenticationTokenCreateResponse,
+    HostAuthenticationTokenRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestHostAuthenticationTokens:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.create()
+        assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.create(
+            token="gho_xxxxxxxxxxxx",
+            expires_at=parse_datetime("2024-12-31T23:59:59Z"),
+            host="github.com",
+            refresh_token="ghr_xxxxxxxxxxxx",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            source="HOST_AUTHENTICATION_TOKEN_SOURCE_OAUTH",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.runners.configurations.host_authentication_tokens.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = response.parse()
+        assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.runners.configurations.host_authentication_tokens.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = response.parse()
+            assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.retrieve()
+        assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.runners.configurations.host_authentication_tokens.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = response.parse()
+        assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.runners.configurations.host_authentication_tokens.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = response.parse()
+            assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.update()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            token="gho_xxxxxxxxxxxx",
+            expires_at=parse_datetime("2024-12-31T23:59:59Z"),
+            refresh_token="ghr_xxxxxxxxxxxx",
+        )
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.runners.configurations.host_authentication_tokens.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = response.parse()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.runners.configurations.host_authentication_tokens.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = response.parse()
+            assert_matches_type(object, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.list()
+        assert_matches_type(SyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.list(
+            token="token",
+            page_size=0,
+            filter={
+                "runner_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                "user_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.runners.configurations.host_authentication_tokens.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = response.parse()
+        assert_matches_type(SyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.runners.configurations.host_authentication_tokens.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = response.parse()
+            assert_matches_type(SyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.delete()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        host_authentication_token = client.runners.configurations.host_authentication_tokens.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.runners.configurations.host_authentication_tokens.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = response.parse()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.runners.configurations.host_authentication_tokens.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = response.parse()
+            assert_matches_type(object, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncHostAuthenticationTokens:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.create()
+        assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.create(
+            token="gho_xxxxxxxxxxxx",
+            expires_at=parse_datetime("2024-12-31T23:59:59Z"),
+            host="github.com",
+            refresh_token="ghr_xxxxxxxxxxxx",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            source="HOST_AUTHENTICATION_TOKEN_SOURCE_OAUTH",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.host_authentication_tokens.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = await response.parse()
+        assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with (
+            async_client.runners.configurations.host_authentication_tokens.with_streaming_response.create()
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = await response.parse()
+            assert_matches_type(HostAuthenticationTokenCreateResponse, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.retrieve()
+        assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.host_authentication_tokens.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = await response.parse()
+        assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with (
+            async_client.runners.configurations.host_authentication_tokens.with_streaming_response.retrieve()
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = await response.parse()
+            assert_matches_type(HostAuthenticationTokenRetrieveResponse, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.update()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            token="gho_xxxxxxxxxxxx",
+            expires_at=parse_datetime("2024-12-31T23:59:59Z"),
+            refresh_token="ghr_xxxxxxxxxxxx",
+        )
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.host_authentication_tokens.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = await response.parse()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with (
+            async_client.runners.configurations.host_authentication_tokens.with_streaming_response.update()
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = await response.parse()
+            assert_matches_type(object, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.list()
+        assert_matches_type(AsyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.list(
+            token="token",
+            page_size=0,
+            filter={
+                "runner_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                "user_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.host_authentication_tokens.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = await response.parse()
+        assert_matches_type(AsyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with (
+            async_client.runners.configurations.host_authentication_tokens.with_streaming_response.list()
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = await response.parse()
+            assert_matches_type(AsyncTokensPage[HostAuthenticationToken], host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.delete()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        host_authentication_token = await async_client.runners.configurations.host_authentication_tokens.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.host_authentication_tokens.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        host_authentication_token = await response.parse()
+        assert_matches_type(object, host_authentication_token, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with (
+            async_client.runners.configurations.host_authentication_tokens.with_streaming_response.delete()
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            host_authentication_token = await response.parse()
+            assert_matches_type(object, host_authentication_token, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/runners/configurations/test_schema.py b/tests/api_resources/runners/configurations/test_schema.py
new file mode 100644
index 0000000..828522f
--- /dev/null
+++ b/tests/api_resources/runners/configurations/test_schema.py
@@ -0,0 +1,96 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types.runners.configurations import SchemaRetrieveResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSchema:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        schema = client.runners.configurations.schema.retrieve()
+        assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        schema = client.runners.configurations.schema.retrieve(
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.runners.configurations.schema.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        schema = response.parse()
+        assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.runners.configurations.schema.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            schema = response.parse()
+            assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSchema:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        schema = await async_client.runners.configurations.schema.retrieve()
+        assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        schema = await async_client.runners.configurations.schema.retrieve(
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.schema.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        schema = await response.parse()
+        assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.schema.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            schema = await response.parse()
+            assert_matches_type(SchemaRetrieveResponse, schema, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/runners/configurations/test_scm_integrations.py b/tests/api_resources/runners/configurations/test_scm_integrations.py
new file mode 100644
index 0000000..d3c0790
--- /dev/null
+++ b/tests/api_resources/runners/configurations/test_scm_integrations.py
@@ -0,0 +1,421 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncIntegrationsPage, AsyncIntegrationsPage
+from gitpod.types.runners.configurations import (
+    ScmIntegration,
+    ScmIntegrationCreateResponse,
+    ScmIntegrationRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestScmIntegrations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.create()
+        assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.create(
+            host="github.com",
+            issuer_url="issuerUrl",
+            oauth_client_id="client_id",
+            oauth_plaintext_client_secret="client_secret",
+            pat=True,
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            scm_id="github",
+        )
+        assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.runners.configurations.scm_integrations.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = response.parse()
+        assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.runners.configurations.scm_integrations.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = response.parse()
+            assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.retrieve()
+        assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.runners.configurations.scm_integrations.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = response.parse()
+        assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.runners.configurations.scm_integrations.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = response.parse()
+            assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.update()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            issuer_url="issuerUrl",
+            oauth_client_id="new_client_id",
+            oauth_plaintext_client_secret="new_client_secret",
+            pat=True,
+        )
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.runners.configurations.scm_integrations.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = response.parse()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.runners.configurations.scm_integrations.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = response.parse()
+            assert_matches_type(object, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.list()
+        assert_matches_type(SyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.list(
+            token="token",
+            page_size=0,
+            filter={"runner_ids": ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.runners.configurations.scm_integrations.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = response.parse()
+        assert_matches_type(SyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.runners.configurations.scm_integrations.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = response.parse()
+            assert_matches_type(SyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.delete()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        scm_integration = client.runners.configurations.scm_integrations.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.runners.configurations.scm_integrations.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = response.parse()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.runners.configurations.scm_integrations.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = response.parse()
+            assert_matches_type(object, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncScmIntegrations:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.create()
+        assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.create(
+            host="github.com",
+            issuer_url="issuerUrl",
+            oauth_client_id="client_id",
+            oauth_plaintext_client_secret="client_secret",
+            pat=True,
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            scm_id="github",
+        )
+        assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.scm_integrations.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = await response.parse()
+        assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.scm_integrations.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = await response.parse()
+            assert_matches_type(ScmIntegrationCreateResponse, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.retrieve()
+        assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.scm_integrations.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = await response.parse()
+        assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.scm_integrations.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = await response.parse()
+            assert_matches_type(ScmIntegrationRetrieveResponse, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.update()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.update(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            issuer_url="issuerUrl",
+            oauth_client_id="new_client_id",
+            oauth_plaintext_client_secret="new_client_secret",
+            pat=True,
+        )
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.scm_integrations.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = await response.parse()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.scm_integrations.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = await response.parse()
+            assert_matches_type(object, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.list()
+        assert_matches_type(AsyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.list(
+            token="token",
+            page_size=0,
+            filter={"runner_ids": ["d2c94c27-3b76-4a42-b88c-95a85e392c68"]},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.scm_integrations.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = await response.parse()
+        assert_matches_type(AsyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.scm_integrations.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = await response.parse()
+            assert_matches_type(AsyncIntegrationsPage[ScmIntegration], scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.delete()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        scm_integration = await async_client.runners.configurations.scm_integrations.delete(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.scm_integrations.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        scm_integration = await response.parse()
+        assert_matches_type(object, scm_integration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.scm_integrations.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            scm_integration = await response.parse()
+            assert_matches_type(object, scm_integration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/runners/test_configurations.py b/tests/api_resources/runners/test_configurations.py
new file mode 100644
index 0000000..94422e1
--- /dev/null
+++ b/tests/api_resources/runners/test_configurations.py
@@ -0,0 +1,142 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types.runners import ConfigurationValidateResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestConfigurations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_validate(self, client: Gitpod) -> None:
+        configuration = client.runners.configurations.validate()
+        assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_validate_with_all_params(self, client: Gitpod) -> None:
+        configuration = client.runners.configurations.validate(
+            environment_class={
+                "id": "id",
+                "runner_id": "runnerId",
+                "configuration": [
+                    {
+                        "key": "key",
+                        "value": "value",
+                    }
+                ],
+                "description": "xxx",
+                "display_name": "xxx",
+                "enabled": True,
+            },
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            scm_integration={
+                "id": "integration-id",
+                "host": "github.com",
+                "issuer_url": "issuerUrl",
+                "oauth_client_id": "client_id",
+                "oauth_encrypted_client_secret": "U3RhaW5sZXNzIHJvY2tz",
+                "oauth_plaintext_client_secret": "client_secret",
+                "pat": True,
+                "scm_id": "github",
+            },
+        )
+        assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_validate(self, client: Gitpod) -> None:
+        response = client.runners.configurations.with_raw_response.validate()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        configuration = response.parse()
+        assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_validate(self, client: Gitpod) -> None:
+        with client.runners.configurations.with_streaming_response.validate() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            configuration = response.parse()
+            assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncConfigurations:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_validate(self, async_client: AsyncGitpod) -> None:
+        configuration = await async_client.runners.configurations.validate()
+        assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_validate_with_all_params(self, async_client: AsyncGitpod) -> None:
+        configuration = await async_client.runners.configurations.validate(
+            environment_class={
+                "id": "id",
+                "runner_id": "runnerId",
+                "configuration": [
+                    {
+                        "key": "key",
+                        "value": "value",
+                    }
+                ],
+                "description": "xxx",
+                "display_name": "xxx",
+                "enabled": True,
+            },
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            scm_integration={
+                "id": "integration-id",
+                "host": "github.com",
+                "issuer_url": "issuerUrl",
+                "oauth_client_id": "client_id",
+                "oauth_encrypted_client_secret": "U3RhaW5sZXNzIHJvY2tz",
+                "oauth_plaintext_client_secret": "client_secret",
+                "pat": True,
+                "scm_id": "github",
+            },
+        )
+        assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_validate(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.configurations.with_raw_response.validate()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        configuration = await response.parse()
+        assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_validate(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.configurations.with_streaming_response.validate() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            configuration = await response.parse()
+            assert_matches_type(ConfigurationValidateResponse, configuration, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/runners/test_policies.py b/tests/api_resources/runners/test_policies.py
new file mode 100644
index 0000000..9f387dd
--- /dev/null
+++ b/tests/api_resources/runners/test_policies.py
@@ -0,0 +1,339 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncPoliciesPage, AsyncPoliciesPage
+from gitpod.types.runners import (
+    RunnerPolicy,
+    PolicyCreateResponse,
+    PolicyUpdateResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPolicies:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        policy = client.runners.policies.create()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        policy = client.runners.policies.create(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            role="RUNNER_ROLE_ADMIN",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.runners.policies.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.runners.policies.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        policy = client.runners.policies.update()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        policy = client.runners.policies.update(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            role="RUNNER_ROLE_USER",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.runners.policies.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.runners.policies.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        policy = client.runners.policies.list()
+        assert_matches_type(SyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        policy = client.runners.policies.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(SyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.runners.policies.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(SyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.runners.policies.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(SyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        policy = client.runners.policies.delete()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        policy = client.runners.policies.delete(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.runners.policies.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = response.parse()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.runners.policies.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = response.parse()
+            assert_matches_type(object, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncPolicies:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.create()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.create(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            role="RUNNER_ROLE_ADMIN",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.policies.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.policies.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(PolicyCreateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.update()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.update(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            role="RUNNER_ROLE_USER",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.policies.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.policies.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(PolicyUpdateResponse, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.list()
+        assert_matches_type(AsyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(AsyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.policies.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(AsyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.policies.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(AsyncPoliciesPage[RunnerPolicy], policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.delete()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        policy = await async_client.runners.policies.delete(
+            group_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.policies.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        policy = await response.parse()
+        assert_matches_type(object, policy, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.policies.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            policy = await response.parse()
+            assert_matches_type(object, policy, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py
new file mode 100644
index 0000000..7955675
--- /dev/null
+++ b/tests/api_resources/test_accounts.py
@@ -0,0 +1,416 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    LoginProvider,
+    AccountRetrieveResponse,
+    AccountGetSSOLoginURLResponse,
+    AccountListJoinableOrganizationsResponse,
+)
+from gitpod.pagination import SyncLoginProvidersPage, AsyncLoginProvidersPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAccounts:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        account = client.accounts.retrieve()
+        assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        account = client.accounts.retrieve(
+            empty=True,
+        )
+        assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.accounts.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = response.parse()
+        assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.accounts.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = response.parse()
+            assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        account = client.accounts.delete(
+            account_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.accounts.with_raw_response.delete(
+            account_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = response.parse()
+        assert_matches_type(object, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.accounts.with_streaming_response.delete(
+            account_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = response.parse()
+            assert_matches_type(object, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_sso_login_url(self, client: Gitpod) -> None:
+        account = client.accounts.get_sso_login_url(
+            email="user@company.com",
+        )
+        assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_sso_login_url_with_all_params(self, client: Gitpod) -> None:
+        account = client.accounts.get_sso_login_url(
+            email="user@company.com",
+            return_to="https://example.com",
+        )
+        assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get_sso_login_url(self, client: Gitpod) -> None:
+        response = client.accounts.with_raw_response.get_sso_login_url(
+            email="user@company.com",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = response.parse()
+        assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get_sso_login_url(self, client: Gitpod) -> None:
+        with client.accounts.with_streaming_response.get_sso_login_url(
+            email="user@company.com",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = response.parse()
+            assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_joinable_organizations(self, client: Gitpod) -> None:
+        account = client.accounts.list_joinable_organizations()
+        assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_joinable_organizations_with_all_params(self, client: Gitpod) -> None:
+        account = client.accounts.list_joinable_organizations(
+            token="token",
+            page_size=0,
+            empty=True,
+        )
+        assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_joinable_organizations(self, client: Gitpod) -> None:
+        response = client.accounts.with_raw_response.list_joinable_organizations()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = response.parse()
+        assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_joinable_organizations(self, client: Gitpod) -> None:
+        with client.accounts.with_streaming_response.list_joinable_organizations() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = response.parse()
+            assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_login_providers(self, client: Gitpod) -> None:
+        account = client.accounts.list_login_providers()
+        assert_matches_type(SyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_login_providers_with_all_params(self, client: Gitpod) -> None:
+        account = client.accounts.list_login_providers(
+            token="token",
+            page_size=0,
+            filter={"invite_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_login_providers(self, client: Gitpod) -> None:
+        response = client.accounts.with_raw_response.list_login_providers()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = response.parse()
+        assert_matches_type(SyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_login_providers(self, client: Gitpod) -> None:
+        with client.accounts.with_streaming_response.list_login_providers() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = response.parse()
+            assert_matches_type(SyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncAccounts:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.retrieve()
+        assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.retrieve(
+            empty=True,
+        )
+        assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.accounts.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = await response.parse()
+        assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.accounts.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = await response.parse()
+            assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.delete(
+            account_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.accounts.with_raw_response.delete(
+            account_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = await response.parse()
+        assert_matches_type(object, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.accounts.with_streaming_response.delete(
+            account_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = await response.parse()
+            assert_matches_type(object, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_sso_login_url(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.get_sso_login_url(
+            email="user@company.com",
+        )
+        assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_sso_login_url_with_all_params(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.get_sso_login_url(
+            email="user@company.com",
+            return_to="https://example.com",
+        )
+        assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get_sso_login_url(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.accounts.with_raw_response.get_sso_login_url(
+            email="user@company.com",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = await response.parse()
+        assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get_sso_login_url(self, async_client: AsyncGitpod) -> None:
+        async with async_client.accounts.with_streaming_response.get_sso_login_url(
+            email="user@company.com",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = await response.parse()
+            assert_matches_type(AccountGetSSOLoginURLResponse, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_joinable_organizations(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.list_joinable_organizations()
+        assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_joinable_organizations_with_all_params(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.list_joinable_organizations(
+            token="token",
+            page_size=0,
+            empty=True,
+        )
+        assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_joinable_organizations(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.accounts.with_raw_response.list_joinable_organizations()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = await response.parse()
+        assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_joinable_organizations(self, async_client: AsyncGitpod) -> None:
+        async with async_client.accounts.with_streaming_response.list_joinable_organizations() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = await response.parse()
+            assert_matches_type(AccountListJoinableOrganizationsResponse, account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_login_providers(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.list_login_providers()
+        assert_matches_type(AsyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_login_providers_with_all_params(self, async_client: AsyncGitpod) -> None:
+        account = await async_client.accounts.list_login_providers(
+            token="token",
+            page_size=0,
+            filter={"invite_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_login_providers(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.accounts.with_raw_response.list_login_providers()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        account = await response.parse()
+        assert_matches_type(AsyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_login_providers(self, async_client: AsyncGitpod) -> None:
+        async with async_client.accounts.with_streaming_response.list_login_providers() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            account = await response.parse()
+            assert_matches_type(AsyncLoginProvidersPage[LoginProvider], account, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_editors.py b/tests/api_resources/test_editors.py
new file mode 100644
index 0000000..14b2938
--- /dev/null
+++ b/tests/api_resources/test_editors.py
@@ -0,0 +1,261 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    Editor,
+    EditorRetrieveResponse,
+    EditorResolveURLResponse,
+)
+from gitpod.pagination import SyncEditorsPage, AsyncEditorsPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEditors:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        editor = client.editors.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(EditorRetrieveResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.editors.with_raw_response.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        editor = response.parse()
+        assert_matches_type(EditorRetrieveResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.editors.with_streaming_response.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            editor = response.parse()
+            assert_matches_type(EditorRetrieveResponse, editor, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        editor = client.editors.list()
+        assert_matches_type(SyncEditorsPage[Editor], editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        editor = client.editors.list(
+            token="token",
+            page_size=0,
+            filter={"allowed_by_policy": True},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncEditorsPage[Editor], editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.editors.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        editor = response.parse()
+        assert_matches_type(SyncEditorsPage[Editor], editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.editors.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            editor = response.parse()
+            assert_matches_type(SyncEditorsPage[Editor], editor, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_resolve_url(self, client: Gitpod) -> None:
+        editor = client.editors.resolve_url(
+            editor_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(EditorResolveURLResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_resolve_url(self, client: Gitpod) -> None:
+        response = client.editors.with_raw_response.resolve_url(
+            editor_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        editor = response.parse()
+        assert_matches_type(EditorResolveURLResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_resolve_url(self, client: Gitpod) -> None:
+        with client.editors.with_streaming_response.resolve_url(
+            editor_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            editor = response.parse()
+            assert_matches_type(EditorResolveURLResponse, editor, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncEditors:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        editor = await async_client.editors.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(EditorRetrieveResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.editors.with_raw_response.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        editor = await response.parse()
+        assert_matches_type(EditorRetrieveResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.editors.with_streaming_response.retrieve(
+            id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            editor = await response.parse()
+            assert_matches_type(EditorRetrieveResponse, editor, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        editor = await async_client.editors.list()
+        assert_matches_type(AsyncEditorsPage[Editor], editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        editor = await async_client.editors.list(
+            token="token",
+            page_size=0,
+            filter={"allowed_by_policy": True},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncEditorsPage[Editor], editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.editors.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        editor = await response.parse()
+        assert_matches_type(AsyncEditorsPage[Editor], editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.editors.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            editor = await response.parse()
+            assert_matches_type(AsyncEditorsPage[Editor], editor, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_resolve_url(self, async_client: AsyncGitpod) -> None:
+        editor = await async_client.editors.resolve_url(
+            editor_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(EditorResolveURLResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_resolve_url(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.editors.with_raw_response.resolve_url(
+            editor_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        editor = await response.parse()
+        assert_matches_type(EditorResolveURLResponse, editor, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_resolve_url(self, async_client: AsyncGitpod) -> None:
+        async with async_client.editors.with_streaming_response.resolve_url(
+            editor_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            editor = await response.parse()
+            assert_matches_type(EditorResolveURLResponse, editor, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_environments.py b/tests/api_resources/test_environments.py
new file mode 100644
index 0000000..861ebc4
--- /dev/null
+++ b/tests/api_resources/test_environments.py
@@ -0,0 +1,1271 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    Environment,
+    EnvironmentCreateResponse,
+    EnvironmentRetrieveResponse,
+    EnvironmentCreateLogsTokenResponse,
+    EnvironmentCreateFromProjectResponse,
+    EnvironmentCreateEnvironmentTokenResponse,
+)
+from gitpod._utils import parse_datetime
+from gitpod.pagination import SyncEnvironmentsPage, AsyncEnvironmentsPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEnvironments:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        environment = client.environments.create()
+        assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.create(
+            spec={
+                "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                "automations_file": {
+                    "automations_file_path": "automationsFilePath",
+                    "session": "session",
+                },
+                "content": {
+                    "git_email": "gitEmail",
+                    "git_username": "gitUsername",
+                    "initializer": {
+                        "specs": [
+                            {
+                                "context_url": {"url": "https://github.com/gitpod-io/gitpod"},
+                                "git": {
+                                    "checkout_location": "checkoutLocation",
+                                    "clone_target": "cloneTarget",
+                                    "remote_uri": "remoteUri",
+                                    "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                                    "upstream_remote_uri": "upstreamRemoteUri",
+                                },
+                            }
+                        ]
+                    },
+                    "session": "session",
+                },
+                "desired_phase": "ENVIRONMENT_PHASE_UNSPECIFIED",
+                "devcontainer": {
+                    "default_devcontainer_image": "defaultDevcontainerImage",
+                    "devcontainer_file_path": "devcontainerFilePath",
+                    "dotfiles": {"repository": "https://example.com"},
+                    "session": "session",
+                },
+                "machine": {
+                    "class": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                    "session": "session",
+                },
+                "ports": [
+                    {
+                        "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                        "name": "x",
+                        "port": 1,
+                    }
+                ],
+                "secrets": [
+                    {
+                        "id": "id",
+                        "container_registry_basic_auth_host": "containerRegistryBasicAuthHost",
+                        "environment_variable": "environmentVariable",
+                        "file_path": "filePath",
+                        "git_credential_host": "gitCredentialHost",
+                        "name": "name",
+                        "session": "session",
+                        "source": "source",
+                        "source_ref": "sourceRef",
+                    }
+                ],
+                "spec_version": "specVersion",
+                "ssh_public_keys": [
+                    {
+                        "id": "id",
+                        "value": "value",
+                    }
+                ],
+                "timeout": {"disconnected": "+9125115.360s"},
+            },
+        )
+        assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        environment = client.environments.retrieve(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(EnvironmentRetrieveResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.retrieve(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(EnvironmentRetrieveResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.retrieve(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(EnvironmentRetrieveResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        environment = client.environments.update()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.update(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            metadata={"name": "name"},
+            spec={
+                "automations_file": {
+                    "automations_file_path": "automationsFilePath",
+                    "session": "session",
+                },
+                "content": {
+                    "git_email": "gitEmail",
+                    "git_username": "gitUsername",
+                    "initializer": {
+                        "specs": [
+                            {
+                                "context_url": {"url": "https://example.com"},
+                                "git": {
+                                    "checkout_location": "checkoutLocation",
+                                    "clone_target": "cloneTarget",
+                                    "remote_uri": "remoteUri",
+                                    "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                                    "upstream_remote_uri": "upstreamRemoteUri",
+                                },
+                            }
+                        ]
+                    },
+                    "session": "session",
+                },
+                "devcontainer": {
+                    "devcontainer_file_path": "devcontainerFilePath",
+                    "session": "session",
+                },
+                "ports": [
+                    {
+                        "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                        "name": "x",
+                        "port": 1,
+                    }
+                ],
+                "ssh_public_keys": [
+                    {
+                        "id": "0194b7c1-c954-718d-91a4-9a742aa5fc11",
+                        "value": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...",
+                    }
+                ],
+                "timeout": {"disconnected": "+9125115.360s"},
+            },
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        environment = client.environments.list()
+        assert_matches_type(SyncEnvironmentsPage[Environment], environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.list(
+            token="token",
+            page_size=0,
+            filter={
+                "archival_status": "ARCHIVAL_STATUS_UNSPECIFIED",
+                "creator_ids": ["f53d2330-3795-4c5d-a1f3-453121af9c60"],
+                "project_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "runner_ids": ["e6aa9c54-89d3-42c1-ac31-bd8d8f1concentrate"],
+                "runner_kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "status_phases": ["ENVIRONMENT_PHASE_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(SyncEnvironmentsPage[Environment], environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(SyncEnvironmentsPage[Environment], environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(SyncEnvironmentsPage[Environment], environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        environment = client.environments.delete()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.delete(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            force=False,
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_environment_token(self, client: Gitpod) -> None:
+        environment = client.environments.create_environment_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(EnvironmentCreateEnvironmentTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create_environment_token(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.create_environment_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(EnvironmentCreateEnvironmentTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create_environment_token(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.create_environment_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(EnvironmentCreateEnvironmentTokenResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_from_project(self, client: Gitpod) -> None:
+        environment = client.environments.create_from_project()
+        assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_from_project_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.create_from_project(
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            spec={
+                "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                "automations_file": {
+                    "automations_file_path": "automationsFilePath",
+                    "session": "session",
+                },
+                "content": {
+                    "git_email": "gitEmail",
+                    "git_username": "gitUsername",
+                    "initializer": {
+                        "specs": [
+                            {
+                                "context_url": {"url": "https://example.com"},
+                                "git": {
+                                    "checkout_location": "checkoutLocation",
+                                    "clone_target": "cloneTarget",
+                                    "remote_uri": "remoteUri",
+                                    "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                                    "upstream_remote_uri": "upstreamRemoteUri",
+                                },
+                            }
+                        ]
+                    },
+                    "session": "session",
+                },
+                "desired_phase": "ENVIRONMENT_PHASE_UNSPECIFIED",
+                "devcontainer": {
+                    "default_devcontainer_image": "defaultDevcontainerImage",
+                    "devcontainer_file_path": "devcontainerFilePath",
+                    "dotfiles": {"repository": "https://example.com"},
+                    "session": "session",
+                },
+                "machine": {
+                    "class": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                    "session": "session",
+                },
+                "ports": [
+                    {
+                        "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                        "name": "x",
+                        "port": 1,
+                    }
+                ],
+                "secrets": [
+                    {
+                        "id": "id",
+                        "container_registry_basic_auth_host": "containerRegistryBasicAuthHost",
+                        "environment_variable": "environmentVariable",
+                        "file_path": "filePath",
+                        "git_credential_host": "gitCredentialHost",
+                        "name": "name",
+                        "session": "session",
+                        "source": "source",
+                        "source_ref": "sourceRef",
+                    }
+                ],
+                "spec_version": "specVersion",
+                "ssh_public_keys": [
+                    {
+                        "id": "id",
+                        "value": "value",
+                    }
+                ],
+                "timeout": {"disconnected": "14400s"},
+            },
+        )
+        assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create_from_project(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.create_from_project()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create_from_project(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.create_from_project() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_logs_token(self, client: Gitpod) -> None:
+        environment = client.environments.create_logs_token()
+        assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_logs_token_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.create_logs_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create_logs_token(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.create_logs_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create_logs_token(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.create_logs_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_mark_active(self, client: Gitpod) -> None:
+        environment = client.environments.mark_active()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_mark_active_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.mark_active(
+            activity_signal={
+                "source": "VS Code",
+                "timestamp": parse_datetime("2025-02-12T14:30:00Z"),
+            },
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_mark_active(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.mark_active()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_mark_active(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.mark_active() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_start(self, client: Gitpod) -> None:
+        environment = client.environments.start()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_start_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.start(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_start(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.start()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_start(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.start() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_stop(self, client: Gitpod) -> None:
+        environment = client.environments.stop()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_stop_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.stop(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_stop(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.stop()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_stop(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.stop() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_unarchive(self, client: Gitpod) -> None:
+        environment = client.environments.unarchive()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_unarchive_with_all_params(self, client: Gitpod) -> None:
+        environment = client.environments.unarchive(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_unarchive(self, client: Gitpod) -> None:
+        response = client.environments.with_raw_response.unarchive()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_unarchive(self, client: Gitpod) -> None:
+        with client.environments.with_streaming_response.unarchive() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncEnvironments:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.create()
+        assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.create(
+            spec={
+                "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                "automations_file": {
+                    "automations_file_path": "automationsFilePath",
+                    "session": "session",
+                },
+                "content": {
+                    "git_email": "gitEmail",
+                    "git_username": "gitUsername",
+                    "initializer": {
+                        "specs": [
+                            {
+                                "context_url": {"url": "https://github.com/gitpod-io/gitpod"},
+                                "git": {
+                                    "checkout_location": "checkoutLocation",
+                                    "clone_target": "cloneTarget",
+                                    "remote_uri": "remoteUri",
+                                    "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                                    "upstream_remote_uri": "upstreamRemoteUri",
+                                },
+                            }
+                        ]
+                    },
+                    "session": "session",
+                },
+                "desired_phase": "ENVIRONMENT_PHASE_UNSPECIFIED",
+                "devcontainer": {
+                    "default_devcontainer_image": "defaultDevcontainerImage",
+                    "devcontainer_file_path": "devcontainerFilePath",
+                    "dotfiles": {"repository": "https://example.com"},
+                    "session": "session",
+                },
+                "machine": {
+                    "class": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                    "session": "session",
+                },
+                "ports": [
+                    {
+                        "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                        "name": "x",
+                        "port": 1,
+                    }
+                ],
+                "secrets": [
+                    {
+                        "id": "id",
+                        "container_registry_basic_auth_host": "containerRegistryBasicAuthHost",
+                        "environment_variable": "environmentVariable",
+                        "file_path": "filePath",
+                        "git_credential_host": "gitCredentialHost",
+                        "name": "name",
+                        "session": "session",
+                        "source": "source",
+                        "source_ref": "sourceRef",
+                    }
+                ],
+                "spec_version": "specVersion",
+                "ssh_public_keys": [
+                    {
+                        "id": "id",
+                        "value": "value",
+                    }
+                ],
+                "timeout": {"disconnected": "+9125115.360s"},
+            },
+        )
+        assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(EnvironmentCreateResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.retrieve(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(EnvironmentRetrieveResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.retrieve(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(EnvironmentRetrieveResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.retrieve(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(EnvironmentRetrieveResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.update()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.update(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            metadata={"name": "name"},
+            spec={
+                "automations_file": {
+                    "automations_file_path": "automationsFilePath",
+                    "session": "session",
+                },
+                "content": {
+                    "git_email": "gitEmail",
+                    "git_username": "gitUsername",
+                    "initializer": {
+                        "specs": [
+                            {
+                                "context_url": {"url": "https://example.com"},
+                                "git": {
+                                    "checkout_location": "checkoutLocation",
+                                    "clone_target": "cloneTarget",
+                                    "remote_uri": "remoteUri",
+                                    "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                                    "upstream_remote_uri": "upstreamRemoteUri",
+                                },
+                            }
+                        ]
+                    },
+                    "session": "session",
+                },
+                "devcontainer": {
+                    "devcontainer_file_path": "devcontainerFilePath",
+                    "session": "session",
+                },
+                "ports": [
+                    {
+                        "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                        "name": "x",
+                        "port": 1,
+                    }
+                ],
+                "ssh_public_keys": [
+                    {
+                        "id": "0194b7c1-c954-718d-91a4-9a742aa5fc11",
+                        "value": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...",
+                    }
+                ],
+                "timeout": {"disconnected": "+9125115.360s"},
+            },
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.list()
+        assert_matches_type(AsyncEnvironmentsPage[Environment], environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.list(
+            token="token",
+            page_size=0,
+            filter={
+                "archival_status": "ARCHIVAL_STATUS_UNSPECIFIED",
+                "creator_ids": ["f53d2330-3795-4c5d-a1f3-453121af9c60"],
+                "project_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "runner_ids": ["e6aa9c54-89d3-42c1-ac31-bd8d8f1concentrate"],
+                "runner_kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "status_phases": ["ENVIRONMENT_PHASE_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(AsyncEnvironmentsPage[Environment], environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(AsyncEnvironmentsPage[Environment], environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(AsyncEnvironmentsPage[Environment], environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.delete()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.delete(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            force=False,
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_environment_token(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.create_environment_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(EnvironmentCreateEnvironmentTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create_environment_token(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.create_environment_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(EnvironmentCreateEnvironmentTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create_environment_token(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.create_environment_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(EnvironmentCreateEnvironmentTokenResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_from_project(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.create_from_project()
+        assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_from_project_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.create_from_project(
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            spec={
+                "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                "automations_file": {
+                    "automations_file_path": "automationsFilePath",
+                    "session": "session",
+                },
+                "content": {
+                    "git_email": "gitEmail",
+                    "git_username": "gitUsername",
+                    "initializer": {
+                        "specs": [
+                            {
+                                "context_url": {"url": "https://example.com"},
+                                "git": {
+                                    "checkout_location": "checkoutLocation",
+                                    "clone_target": "cloneTarget",
+                                    "remote_uri": "remoteUri",
+                                    "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                                    "upstream_remote_uri": "upstreamRemoteUri",
+                                },
+                            }
+                        ]
+                    },
+                    "session": "session",
+                },
+                "desired_phase": "ENVIRONMENT_PHASE_UNSPECIFIED",
+                "devcontainer": {
+                    "default_devcontainer_image": "defaultDevcontainerImage",
+                    "devcontainer_file_path": "devcontainerFilePath",
+                    "dotfiles": {"repository": "https://example.com"},
+                    "session": "session",
+                },
+                "machine": {
+                    "class": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                    "session": "session",
+                },
+                "ports": [
+                    {
+                        "admission": "ADMISSION_LEVEL_UNSPECIFIED",
+                        "name": "x",
+                        "port": 1,
+                    }
+                ],
+                "secrets": [
+                    {
+                        "id": "id",
+                        "container_registry_basic_auth_host": "containerRegistryBasicAuthHost",
+                        "environment_variable": "environmentVariable",
+                        "file_path": "filePath",
+                        "git_credential_host": "gitCredentialHost",
+                        "name": "name",
+                        "session": "session",
+                        "source": "source",
+                        "source_ref": "sourceRef",
+                    }
+                ],
+                "spec_version": "specVersion",
+                "ssh_public_keys": [
+                    {
+                        "id": "id",
+                        "value": "value",
+                    }
+                ],
+                "timeout": {"disconnected": "14400s"},
+            },
+        )
+        assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create_from_project(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.create_from_project()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create_from_project(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.create_from_project() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(EnvironmentCreateFromProjectResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_logs_token(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.create_logs_token()
+        assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_logs_token_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.create_logs_token(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create_logs_token(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.create_logs_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create_logs_token(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.create_logs_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(EnvironmentCreateLogsTokenResponse, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_mark_active(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.mark_active()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_mark_active_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.mark_active(
+            activity_signal={
+                "source": "VS Code",
+                "timestamp": parse_datetime("2025-02-12T14:30:00Z"),
+            },
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_mark_active(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.mark_active()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_mark_active(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.mark_active() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_start(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.start()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_start_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.start(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_start(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.start()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_start(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.start() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_stop(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.stop()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_stop_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.stop(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_stop(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.stop()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_stop(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.stop() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_unarchive(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.unarchive()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_unarchive_with_all_params(self, async_client: AsyncGitpod) -> None:
+        environment = await async_client.environments.unarchive(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+        )
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_unarchive(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.environments.with_raw_response.unarchive()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        environment = await response.parse()
+        assert_matches_type(object, environment, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_unarchive(self, async_client: AsyncGitpod) -> None:
+        async with async_client.environments.with_streaming_response.unarchive() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            environment = await response.parse()
+            assert_matches_type(object, environment, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_events.py b/tests/api_resources/test_events.py
new file mode 100644
index 0000000..20a56a2
--- /dev/null
+++ b/tests/api_resources/test_events.py
@@ -0,0 +1,192 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import EventListResponse, EventWatchResponse
+from gitpod.pagination import SyncEntriesPage, AsyncEntriesPage
+from gitpod._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEvents:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        event = client.events.list()
+        assert_matches_type(SyncEntriesPage[EventListResponse], event, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        event = client.events.list(
+            token="token",
+            page_size=0,
+            filter={
+                "actor_ids": ["d2c94c27-3b76-4a42-b88c-95a85e392c68"],
+                "actor_principals": ["PRINCIPAL_USER"],
+                "subject_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "subject_types": ["RESOURCE_TYPE_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncEntriesPage[EventListResponse], event, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.events.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        event = response.parse()
+        assert_matches_type(SyncEntriesPage[EventListResponse], event, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.events.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            event = response.parse()
+            assert_matches_type(SyncEntriesPage[EventListResponse], event, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    def test_method_watch(self, client: Gitpod) -> None:
+        event_stream = client.events.watch()
+        assert_matches_type(JSONLDecoder[EventWatchResponse], event_stream, path=["response"])
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    def test_method_watch_with_all_params(self, client: Gitpod) -> None:
+        event_stream = client.events.watch(
+            environment_id="environmentId",
+            organization=True,
+        )
+        assert_matches_type(JSONLDecoder[EventWatchResponse], event_stream, path=["response"])
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    def test_raw_response_watch(self, client: Gitpod) -> None:
+        response = client.events.with_raw_response.watch()
+
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        stream = response.parse()
+        stream.close()
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    def test_streaming_response_watch(self, client: Gitpod) -> None:
+        with client.events.with_streaming_response.watch() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            stream = response.parse()
+            stream.close()
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncEvents:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        event = await async_client.events.list()
+        assert_matches_type(AsyncEntriesPage[EventListResponse], event, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        event = await async_client.events.list(
+            token="token",
+            page_size=0,
+            filter={
+                "actor_ids": ["d2c94c27-3b76-4a42-b88c-95a85e392c68"],
+                "actor_principals": ["PRINCIPAL_USER"],
+                "subject_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "subject_types": ["RESOURCE_TYPE_UNSPECIFIED"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncEntriesPage[EventListResponse], event, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.events.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        event = await response.parse()
+        assert_matches_type(AsyncEntriesPage[EventListResponse], event, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.events.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            event = await response.parse()
+            assert_matches_type(AsyncEntriesPage[EventListResponse], event, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    async def test_method_watch(self, async_client: AsyncGitpod) -> None:
+        event_stream = await async_client.events.watch()
+        assert_matches_type(AsyncJSONLDecoder[EventWatchResponse], event_stream, path=["response"])
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    async def test_method_watch_with_all_params(self, async_client: AsyncGitpod) -> None:
+        event_stream = await async_client.events.watch(
+            environment_id="environmentId",
+            organization=True,
+        )
+        assert_matches_type(AsyncJSONLDecoder[EventWatchResponse], event_stream, path=["response"])
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    async def test_raw_response_watch(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.events.with_raw_response.watch()
+
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        stream = await response.parse()
+        await stream.close()
+
+    @pytest.mark.skip(reason="Prism doesn't support JSONL responses yet")
+    @parametrize
+    async def test_streaming_response_watch(self, async_client: AsyncGitpod) -> None:
+        async with async_client.events.with_streaming_response.watch() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            stream = await response.parse()
+            await stream.close()
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_gateways.py b/tests/api_resources/test_gateways.py
new file mode 100644
index 0000000..d4684fb
--- /dev/null
+++ b/tests/api_resources/test_gateways.py
@@ -0,0 +1,107 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncGatewaysPage, AsyncGatewaysPage
+from gitpod.types.shared import Gateway
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestGateways:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        gateway = client.gateways.list()
+        assert_matches_type(SyncGatewaysPage[Gateway], gateway, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        gateway = client.gateways.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(SyncGatewaysPage[Gateway], gateway, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.gateways.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        gateway = response.parse()
+        assert_matches_type(SyncGatewaysPage[Gateway], gateway, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.gateways.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            gateway = response.parse()
+            assert_matches_type(SyncGatewaysPage[Gateway], gateway, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncGateways:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        gateway = await async_client.gateways.list()
+        assert_matches_type(AsyncGatewaysPage[Gateway], gateway, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        gateway = await async_client.gateways.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(AsyncGatewaysPage[Gateway], gateway, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.gateways.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        gateway = await response.parse()
+        assert_matches_type(AsyncGatewaysPage[Gateway], gateway, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.gateways.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            gateway = await response.parse()
+            assert_matches_type(AsyncGatewaysPage[Gateway], gateway, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_groups.py b/tests/api_resources/test_groups.py
new file mode 100644
index 0000000..e0f0936
--- /dev/null
+++ b/tests/api_resources/test_groups.py
@@ -0,0 +1,107 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import Group
+from gitpod.pagination import SyncGroupsPage, AsyncGroupsPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestGroups:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        group = client.groups.list()
+        assert_matches_type(SyncGroupsPage[Group], group, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        group = client.groups.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncGroupsPage[Group], group, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.groups.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        group = response.parse()
+        assert_matches_type(SyncGroupsPage[Group], group, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.groups.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            group = response.parse()
+            assert_matches_type(SyncGroupsPage[Group], group, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncGroups:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        group = await async_client.groups.list()
+        assert_matches_type(AsyncGroupsPage[Group], group, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        group = await async_client.groups.list(
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncGroupsPage[Group], group, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.groups.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        group = await response.parse()
+        assert_matches_type(AsyncGroupsPage[Group], group, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.groups.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            group = await response.parse()
+            assert_matches_type(AsyncGroupsPage[Group], group, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_identity.py b/tests/api_resources/test_identity.py
new file mode 100644
index 0000000..5fbbe49
--- /dev/null
+++ b/tests/api_resources/test_identity.py
@@ -0,0 +1,246 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    IdentityGetIDTokenResponse,
+    IdentityExchangeTokenResponse,
+    IdentityGetAuthenticatedIdentityResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestIdentity:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_exchange_token(self, client: Gitpod) -> None:
+        identity = client.identity.exchange_token()
+        assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_exchange_token_with_all_params(self, client: Gitpod) -> None:
+        identity = client.identity.exchange_token(
+            exchange_token="exchange-token-value",
+        )
+        assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_exchange_token(self, client: Gitpod) -> None:
+        response = client.identity.with_raw_response.exchange_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        identity = response.parse()
+        assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_exchange_token(self, client: Gitpod) -> None:
+        with client.identity.with_streaming_response.exchange_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            identity = response.parse()
+            assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_authenticated_identity(self, client: Gitpod) -> None:
+        identity = client.identity.get_authenticated_identity()
+        assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_authenticated_identity_with_all_params(self, client: Gitpod) -> None:
+        identity = client.identity.get_authenticated_identity(
+            empty=True,
+        )
+        assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get_authenticated_identity(self, client: Gitpod) -> None:
+        response = client.identity.with_raw_response.get_authenticated_identity()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        identity = response.parse()
+        assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get_authenticated_identity(self, client: Gitpod) -> None:
+        with client.identity.with_streaming_response.get_authenticated_identity() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            identity = response.parse()
+            assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_id_token(self, client: Gitpod) -> None:
+        identity = client.identity.get_id_token()
+        assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_id_token_with_all_params(self, client: Gitpod) -> None:
+        identity = client.identity.get_id_token(
+            audience=["https://api.gitpod.io", "https://ws.gitpod.io"],
+            version="ID_TOKEN_VERSION_UNSPECIFIED",
+        )
+        assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get_id_token(self, client: Gitpod) -> None:
+        response = client.identity.with_raw_response.get_id_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        identity = response.parse()
+        assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get_id_token(self, client: Gitpod) -> None:
+        with client.identity.with_streaming_response.get_id_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            identity = response.parse()
+            assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncIdentity:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_exchange_token(self, async_client: AsyncGitpod) -> None:
+        identity = await async_client.identity.exchange_token()
+        assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_exchange_token_with_all_params(self, async_client: AsyncGitpod) -> None:
+        identity = await async_client.identity.exchange_token(
+            exchange_token="exchange-token-value",
+        )
+        assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_exchange_token(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.identity.with_raw_response.exchange_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        identity = await response.parse()
+        assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_exchange_token(self, async_client: AsyncGitpod) -> None:
+        async with async_client.identity.with_streaming_response.exchange_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            identity = await response.parse()
+            assert_matches_type(IdentityExchangeTokenResponse, identity, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_authenticated_identity(self, async_client: AsyncGitpod) -> None:
+        identity = await async_client.identity.get_authenticated_identity()
+        assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_authenticated_identity_with_all_params(self, async_client: AsyncGitpod) -> None:
+        identity = await async_client.identity.get_authenticated_identity(
+            empty=True,
+        )
+        assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get_authenticated_identity(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.identity.with_raw_response.get_authenticated_identity()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        identity = await response.parse()
+        assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get_authenticated_identity(self, async_client: AsyncGitpod) -> None:
+        async with async_client.identity.with_streaming_response.get_authenticated_identity() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            identity = await response.parse()
+            assert_matches_type(IdentityGetAuthenticatedIdentityResponse, identity, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_id_token(self, async_client: AsyncGitpod) -> None:
+        identity = await async_client.identity.get_id_token()
+        assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_id_token_with_all_params(self, async_client: AsyncGitpod) -> None:
+        identity = await async_client.identity.get_id_token(
+            audience=["https://api.gitpod.io", "https://ws.gitpod.io"],
+            version="ID_TOKEN_VERSION_UNSPECIFIED",
+        )
+        assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get_id_token(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.identity.with_raw_response.get_id_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        identity = await response.parse()
+        assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get_id_token(self, async_client: AsyncGitpod) -> None:
+        async with async_client.identity.with_streaming_response.get_id_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            identity = await response.parse()
+            assert_matches_type(IdentityGetIDTokenResponse, identity, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
new file mode 100644
index 0000000..69138c9
--- /dev/null
+++ b/tests/api_resources/test_organizations.py
@@ -0,0 +1,675 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    OrganizationMember,
+    OrganizationJoinResponse,
+    OrganizationCreateResponse,
+    OrganizationUpdateResponse,
+    OrganizationRetrieveResponse,
+)
+from gitpod.pagination import SyncMembersPage, AsyncMembersPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestOrganizations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        organization = client.organizations.create(
+            name="Acme Corp Engineering",
+        )
+        assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        organization = client.organizations.create(
+            name="Acme Corp Engineering",
+            invite_accounts_with_matching_domain=True,
+            join_organization=True,
+        )
+        assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.create(
+            name="Acme Corp Engineering",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.create(
+            name="Acme Corp Engineering",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        organization = client.organizations.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(OrganizationRetrieveResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(OrganizationRetrieveResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(OrganizationRetrieveResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        organization = client.organizations.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        organization = client.organizations.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            invite_domains={"domains": ["sfN2.l.iJR-BU.u9JV9.a.m.o2D-4b-Jd.0Z-kX.L.n.S.f.UKbxB"]},
+            name="name",
+        )
+        assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        organization = client.organizations.delete(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.delete(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.delete(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(object, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_join(self, client: Gitpod) -> None:
+        organization = client.organizations.join()
+        assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_join_with_all_params(self, client: Gitpod) -> None:
+        organization = client.organizations.join(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_join(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.join()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_join(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.join() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_leave(self, client: Gitpod) -> None:
+        organization = client.organizations.leave(
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_leave(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.leave(
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_leave(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.leave(
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(object, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_members(self, client: Gitpod) -> None:
+        organization = client.organizations.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(SyncMembersPage[OrganizationMember], organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_members_with_all_params(self, client: Gitpod) -> None:
+        organization = client.organizations.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncMembersPage[OrganizationMember], organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_members(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(SyncMembersPage[OrganizationMember], organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_members(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(SyncMembersPage[OrganizationMember], organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_set_role(self, client: Gitpod) -> None:
+        organization = client.organizations.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_set_role_with_all_params(self, client: Gitpod) -> None:
+        organization = client.organizations.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            role="ORGANIZATION_ROLE_MEMBER",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_set_role(self, client: Gitpod) -> None:
+        response = client.organizations.with_raw_response.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_set_role(self, client: Gitpod) -> None:
+        with client.organizations.with_streaming_response.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(object, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncOrganizations:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.create(
+            name="Acme Corp Engineering",
+        )
+        assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.create(
+            name="Acme Corp Engineering",
+            invite_accounts_with_matching_domain=True,
+            join_organization=True,
+        )
+        assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.create(
+            name="Acme Corp Engineering",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.create(
+            name="Acme Corp Engineering",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(OrganizationCreateResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(OrganizationRetrieveResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(OrganizationRetrieveResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.retrieve(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(OrganizationRetrieveResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            invite_domains={"domains": ["sfN2.l.iJR-BU.u9JV9.a.m.o2D-4b-Jd.0Z-kX.L.n.S.f.UKbxB"]},
+            name="name",
+        )
+        assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.update(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(OrganizationUpdateResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.delete(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.delete(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.delete(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(object, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_join(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.join()
+        assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_join_with_all_params(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.join(
+            invite_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_join(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.join()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_join(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.join() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(OrganizationJoinResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_leave(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.leave(
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_leave(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.leave(
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_leave(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.leave(
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(object, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_members(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(AsyncMembersPage[OrganizationMember], organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_members_with_all_params(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            token="token",
+            page_size=0,
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncMembersPage[OrganizationMember], organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_members(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(AsyncMembersPage[OrganizationMember], organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_members(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.list_members(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(AsyncMembersPage[OrganizationMember], organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_set_role(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_set_role_with_all_params(self, async_client: AsyncGitpod) -> None:
+        organization = await async_client.organizations.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+            role="ORGANIZATION_ROLE_MEMBER",
+        )
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_set_role(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.organizations.with_raw_response.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(object, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_set_role(self, async_client: AsyncGitpod) -> None:
+        async with async_client.organizations.with_streaming_response.set_role(
+            organization_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(object, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
new file mode 100644
index 0000000..217408d
--- /dev/null
+++ b/tests/api_resources/test_projects.py
@@ -0,0 +1,581 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    Project,
+    ProjectCreateResponse,
+    ProjectUpdateResponse,
+    ProjectRetrieveResponse,
+    ProjectCreateFromEnvironmentResponse,
+)
+from gitpod.pagination import SyncProjectsPage, AsyncProjectsPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestProjects:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        project = client.projects.create(
+            environment_class={},
+            initializer={},
+        )
+        assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        project = client.projects.create(
+            environment_class={
+                "environment_class_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                "local_runner": True,
+            },
+            initializer={
+                "specs": [
+                    {
+                        "context_url": {"url": "https://example.com"},
+                        "git": {
+                            "checkout_location": "checkoutLocation",
+                            "clone_target": "cloneTarget",
+                            "remote_uri": "https://github.com/org/repo",
+                            "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                            "upstream_remote_uri": "upstreamRemoteUri",
+                        },
+                    }
+                ]
+            },
+            automations_file_path="automationsFilePath",
+            devcontainer_file_path="devcontainerFilePath",
+            name="Web Application",
+            technical_description="technicalDescription",
+        )
+        assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.projects.with_raw_response.create(
+            environment_class={},
+            initializer={},
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.projects.with_streaming_response.create(
+            environment_class={},
+            initializer={},
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        project = client.projects.retrieve()
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        project = client.projects.retrieve(
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.projects.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.projects.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        project = client.projects.update()
+        assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        project = client.projects.update(
+            automations_file_path="automationsFilePath",
+            devcontainer_file_path="devcontainerFilePath",
+            environment_class={
+                "environment_class_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                "local_runner": True,
+            },
+            initializer={
+                "specs": [
+                    {
+                        "context_url": {"url": "https://example.com"},
+                        "git": {
+                            "checkout_location": "checkoutLocation",
+                            "clone_target": "cloneTarget",
+                            "remote_uri": "remoteUri",
+                            "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                            "upstream_remote_uri": "upstreamRemoteUri",
+                        },
+                    }
+                ]
+            },
+            name="x",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            technical_description="technicalDescription",
+        )
+        assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.projects.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.projects.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        project = client.projects.list()
+        assert_matches_type(SyncProjectsPage[Project], project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        project = client.projects.list(
+            token="token",
+            page_size=0,
+            filter={"project_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"]},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncProjectsPage[Project], project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.projects.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(SyncProjectsPage[Project], project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.projects.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(SyncProjectsPage[Project], project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        project = client.projects.delete()
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        project = client.projects.delete(
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.projects.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.projects.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(object, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_from_environment(self, client: Gitpod) -> None:
+        project = client.projects.create_from_environment()
+        assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_from_environment_with_all_params(self, client: Gitpod) -> None:
+        project = client.projects.create_from_environment(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            name="Frontend Project",
+        )
+        assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create_from_environment(self, client: Gitpod) -> None:
+        response = client.projects.with_raw_response.create_from_environment()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create_from_environment(self, client: Gitpod) -> None:
+        with client.projects.with_streaming_response.create_from_environment() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncProjects:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.create(
+            environment_class={},
+            initializer={},
+        )
+        assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.create(
+            environment_class={
+                "environment_class_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                "local_runner": True,
+            },
+            initializer={
+                "specs": [
+                    {
+                        "context_url": {"url": "https://example.com"},
+                        "git": {
+                            "checkout_location": "checkoutLocation",
+                            "clone_target": "cloneTarget",
+                            "remote_uri": "https://github.com/org/repo",
+                            "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                            "upstream_remote_uri": "upstreamRemoteUri",
+                        },
+                    }
+                ]
+            },
+            automations_file_path="automationsFilePath",
+            devcontainer_file_path="devcontainerFilePath",
+            name="Web Application",
+            technical_description="technicalDescription",
+        )
+        assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.with_raw_response.create(
+            environment_class={},
+            initializer={},
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.with_streaming_response.create(
+            environment_class={},
+            initializer={},
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectCreateResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.retrieve()
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.retrieve(
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.update()
+        assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.update(
+            automations_file_path="automationsFilePath",
+            devcontainer_file_path="devcontainerFilePath",
+            environment_class={
+                "environment_class_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+                "local_runner": True,
+            },
+            initializer={
+                "specs": [
+                    {
+                        "context_url": {"url": "https://example.com"},
+                        "git": {
+                            "checkout_location": "checkoutLocation",
+                            "clone_target": "cloneTarget",
+                            "remote_uri": "remoteUri",
+                            "target_mode": "CLONE_TARGET_MODE_UNSPECIFIED",
+                            "upstream_remote_uri": "upstreamRemoteUri",
+                        },
+                    }
+                ]
+            },
+            name="x",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            technical_description="technicalDescription",
+        )
+        assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectUpdateResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.list()
+        assert_matches_type(AsyncProjectsPage[Project], project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.list(
+            token="token",
+            page_size=0,
+            filter={"project_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"]},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncProjectsPage[Project], project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(AsyncProjectsPage[Project], project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(AsyncProjectsPage[Project], project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.delete()
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.delete(
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(object, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_from_environment(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.create_from_environment()
+        assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_from_environment_with_all_params(self, async_client: AsyncGitpod) -> None:
+        project = await async_client.projects.create_from_environment(
+            environment_id="07e03a28-65a5-4d98-b532-8ea67b188048",
+            name="Frontend Project",
+        )
+        assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create_from_environment(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.projects.with_raw_response.create_from_environment()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create_from_environment(self, async_client: AsyncGitpod) -> None:
+        async with async_client.projects.with_streaming_response.create_from_environment() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectCreateFromEnvironmentResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_runners.py b/tests/api_resources/test_runners.py
new file mode 100644
index 0000000..5fc76d7
--- /dev/null
+++ b/tests/api_resources/test_runners.py
@@ -0,0 +1,704 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    Runner,
+    RunnerCreateResponse,
+    RunnerRetrieveResponse,
+    RunnerParseContextURLResponse,
+    RunnerCreateRunnerTokenResponse,
+    RunnerCheckAuthenticationForHostResponse,
+)
+from gitpod.pagination import SyncRunnersPage, AsyncRunnersPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRunners:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        runner = client.runners.create()
+        assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.create(
+            kind="RUNNER_KIND_UNSPECIFIED",
+            name="Production Runner",
+            provider="RUNNER_PROVIDER_AWS_EC2",
+            runner_manager_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            spec={
+                "configuration": {
+                    "auto_update": True,
+                    "devcontainer_image_cache_enabled": True,
+                    "log_level": "LOG_LEVEL_UNSPECIFIED",
+                    "metrics": {
+                        "enabled": True,
+                        "password": "password",
+                        "url": "url",
+                        "username": "username",
+                    },
+                    "region": "us-west",
+                    "release_channel": "RUNNER_RELEASE_CHANNEL_STABLE",
+                },
+                "desired_phase": "RUNNER_PHASE_ACTIVE",
+            },
+        )
+        assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Gitpod) -> None:
+        runner = client.runners.retrieve()
+        assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.retrieve(
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Gitpod) -> None:
+        runner = client.runners.update()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.update(
+            name="Updated Runner Name",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            spec={
+                "configuration": {
+                    "auto_update": True,
+                    "devcontainer_image_cache_enabled": True,
+                    "log_level": "LOG_LEVEL_UNSPECIFIED",
+                    "metrics": {
+                        "enabled": True,
+                        "password": "password",
+                        "url": "url",
+                        "username": "username",
+                    },
+                    "release_channel": "RUNNER_RELEASE_CHANNEL_LATEST",
+                },
+                "desired_phase": "RUNNER_PHASE_UNSPECIFIED",
+            },
+        )
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(object, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        runner = client.runners.list()
+        assert_matches_type(SyncRunnersPage[Runner], runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.list(
+            token="token",
+            page_size=0,
+            filter={
+                "creator_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "providers": ["RUNNER_PROVIDER_AWS_EC2"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncRunnersPage[Runner], runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(SyncRunnersPage[Runner], runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(SyncRunnersPage[Runner], runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        runner = client.runners.delete()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.delete(
+            force=True,
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(object, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_check_authentication_for_host(self, client: Gitpod) -> None:
+        runner = client.runners.check_authentication_for_host()
+        assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_check_authentication_for_host_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.check_authentication_for_host(
+            host="github.com",
+            runner_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_check_authentication_for_host(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.check_authentication_for_host()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_check_authentication_for_host(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.check_authentication_for_host() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_runner_token(self, client: Gitpod) -> None:
+        runner = client.runners.create_runner_token()
+        assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_runner_token_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.create_runner_token(
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create_runner_token(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.create_runner_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create_runner_token(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.create_runner_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_parse_context_url(self, client: Gitpod) -> None:
+        runner = client.runners.parse_context_url()
+        assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_parse_context_url_with_all_params(self, client: Gitpod) -> None:
+        runner = client.runners.parse_context_url(
+            context_url="https://github.com/org/repo/tree/main",
+            runner_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_parse_context_url(self, client: Gitpod) -> None:
+        response = client.runners.with_raw_response.parse_context_url()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = response.parse()
+        assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_parse_context_url(self, client: Gitpod) -> None:
+        with client.runners.with_streaming_response.parse_context_url() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = response.parse()
+            assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncRunners:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.create()
+        assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.create(
+            kind="RUNNER_KIND_UNSPECIFIED",
+            name="Production Runner",
+            provider="RUNNER_PROVIDER_AWS_EC2",
+            runner_manager_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            spec={
+                "configuration": {
+                    "auto_update": True,
+                    "devcontainer_image_cache_enabled": True,
+                    "log_level": "LOG_LEVEL_UNSPECIFIED",
+                    "metrics": {
+                        "enabled": True,
+                        "password": "password",
+                        "url": "url",
+                        "username": "username",
+                    },
+                    "region": "us-west",
+                    "release_channel": "RUNNER_RELEASE_CHANNEL_STABLE",
+                },
+                "desired_phase": "RUNNER_PHASE_ACTIVE",
+            },
+        )
+        assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(RunnerCreateResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.retrieve()
+        assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.retrieve(
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(RunnerRetrieveResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.update()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.update(
+            name="Updated Runner Name",
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            spec={
+                "configuration": {
+                    "auto_update": True,
+                    "devcontainer_image_cache_enabled": True,
+                    "log_level": "LOG_LEVEL_UNSPECIFIED",
+                    "metrics": {
+                        "enabled": True,
+                        "password": "password",
+                        "url": "url",
+                        "username": "username",
+                    },
+                    "release_channel": "RUNNER_RELEASE_CHANNEL_LATEST",
+                },
+                "desired_phase": "RUNNER_PHASE_UNSPECIFIED",
+            },
+        )
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.update()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.update() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(object, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.list()
+        assert_matches_type(AsyncRunnersPage[Runner], runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.list(
+            token="token",
+            page_size=0,
+            filter={
+                "creator_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "kinds": ["RUNNER_KIND_UNSPECIFIED"],
+                "providers": ["RUNNER_PROVIDER_AWS_EC2"],
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncRunnersPage[Runner], runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(AsyncRunnersPage[Runner], runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(AsyncRunnersPage[Runner], runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.delete()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.delete(
+            force=True,
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(object, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(object, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_check_authentication_for_host(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.check_authentication_for_host()
+        assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_check_authentication_for_host_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.check_authentication_for_host(
+            host="github.com",
+            runner_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_check_authentication_for_host(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.check_authentication_for_host()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_check_authentication_for_host(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.check_authentication_for_host() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(RunnerCheckAuthenticationForHostResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_runner_token(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.create_runner_token()
+        assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_runner_token_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.create_runner_token(
+            runner_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create_runner_token(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.create_runner_token()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create_runner_token(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.create_runner_token() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(RunnerCreateRunnerTokenResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_parse_context_url(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.parse_context_url()
+        assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_parse_context_url_with_all_params(self, async_client: AsyncGitpod) -> None:
+        runner = await async_client.runners.parse_context_url(
+            context_url="https://github.com/org/repo/tree/main",
+            runner_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_parse_context_url(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.runners.with_raw_response.parse_context_url()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        runner = await response.parse()
+        assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_parse_context_url(self, async_client: AsyncGitpod) -> None:
+        async with async_client.runners.with_streaming_response.parse_context_url() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            runner = await response.parse()
+            assert_matches_type(RunnerParseContextURLResponse, runner, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_secrets.py b/tests/api_resources/test_secrets.py
new file mode 100644
index 0000000..1b215c5
--- /dev/null
+++ b/tests/api_resources/test_secrets.py
@@ -0,0 +1,433 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import (
+    Secret,
+    SecretCreateResponse,
+    SecretGetValueResponse,
+)
+from gitpod.pagination import SyncSecretsPage, AsyncSecretsPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSecrets:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Gitpod) -> None:
+        secret = client.secrets.create()
+        assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Gitpod) -> None:
+        secret = client.secrets.create(
+            container_registry_basic_auth_host="containerRegistryBasicAuthHost",
+            environment_variable=True,
+            file_path="filePath",
+            name="DATABASE_URL",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            scope={
+                "project_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                "user_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            },
+            value="postgresql://user:pass@localhost:5432/db",
+        )
+        assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Gitpod) -> None:
+        response = client.secrets.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = response.parse()
+        assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Gitpod) -> None:
+        with client.secrets.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = response.parse()
+            assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        secret = client.secrets.list()
+        assert_matches_type(SyncSecretsPage[Secret], secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        secret = client.secrets.list(
+            token="token",
+            page_size=0,
+            filter={
+                "project_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "scope": {
+                    "project_id": "b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+                    "user_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                },
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncSecretsPage[Secret], secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.secrets.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = response.parse()
+        assert_matches_type(SyncSecretsPage[Secret], secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.secrets.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = response.parse()
+            assert_matches_type(SyncSecretsPage[Secret], secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        secret = client.secrets.delete()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        secret = client.secrets.delete(
+            secret_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.secrets.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = response.parse()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.secrets.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = response.parse()
+            assert_matches_type(object, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_value(self, client: Gitpod) -> None:
+        secret = client.secrets.get_value()
+        assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_value_with_all_params(self, client: Gitpod) -> None:
+        secret = client.secrets.get_value(
+            secret_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get_value(self, client: Gitpod) -> None:
+        response = client.secrets.with_raw_response.get_value()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = response.parse()
+        assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get_value(self, client: Gitpod) -> None:
+        with client.secrets.with_streaming_response.get_value() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = response.parse()
+            assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_value(self, client: Gitpod) -> None:
+        secret = client.secrets.update_value()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_value_with_all_params(self, client: Gitpod) -> None:
+        secret = client.secrets.update_value(
+            secret_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            value="new-secret-value",
+        )
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update_value(self, client: Gitpod) -> None:
+        response = client.secrets.with_raw_response.update_value()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = response.parse()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update_value(self, client: Gitpod) -> None:
+        with client.secrets.with_streaming_response.update_value() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = response.parse()
+            assert_matches_type(object, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSecrets:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.create()
+        assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.create(
+            container_registry_basic_auth_host="containerRegistryBasicAuthHost",
+            environment_variable=True,
+            file_path="filePath",
+            name="DATABASE_URL",
+            project_id="b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+            scope={
+                "project_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                "user_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            },
+            value="postgresql://user:pass@localhost:5432/db",
+        )
+        assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.secrets.with_raw_response.create()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = await response.parse()
+        assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncGitpod) -> None:
+        async with async_client.secrets.with_streaming_response.create() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = await response.parse()
+            assert_matches_type(SecretCreateResponse, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.list()
+        assert_matches_type(AsyncSecretsPage[Secret], secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.list(
+            token="token",
+            page_size=0,
+            filter={
+                "project_ids": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+                "scope": {
+                    "project_id": "b0e12f6c-4c67-429d-a4a6-d9838b5da047",
+                    "user_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                },
+            },
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncSecretsPage[Secret], secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.secrets.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = await response.parse()
+        assert_matches_type(AsyncSecretsPage[Secret], secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.secrets.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = await response.parse()
+            assert_matches_type(AsyncSecretsPage[Secret], secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.delete()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.delete(
+            secret_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.secrets.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = await response.parse()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.secrets.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = await response.parse()
+            assert_matches_type(object, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_value(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.get_value()
+        assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_value_with_all_params(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.get_value(
+            secret_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get_value(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.secrets.with_raw_response.get_value()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = await response.parse()
+        assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get_value(self, async_client: AsyncGitpod) -> None:
+        async with async_client.secrets.with_streaming_response.get_value() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = await response.parse()
+            assert_matches_type(SecretGetValueResponse, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_value(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.update_value()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_value_with_all_params(self, async_client: AsyncGitpod) -> None:
+        secret = await async_client.secrets.update_value(
+            secret_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            value="new-secret-value",
+        )
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update_value(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.secrets.with_raw_response.update_value()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        secret = await response.parse()
+        assert_matches_type(object, secret, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update_value(self, async_client: AsyncGitpod) -> None:
+        async with async_client.secrets.with_streaming_response.update_value() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            secret = await response.parse()
+            assert_matches_type(object, secret, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_usage.py b/tests/api_resources/test_usage.py
new file mode 100644
index 0000000..e92f8ae
--- /dev/null
+++ b/tests/api_resources/test_usage.py
@@ -0,0 +1,122 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import EnvironmentUsageRecord
+from gitpod._utils import parse_datetime
+from gitpod.pagination import SyncRecordsPage, AsyncRecordsPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestUsage:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_environment_runtime_records(self, client: Gitpod) -> None:
+        usage = client.usage.list_environment_runtime_records()
+        assert_matches_type(SyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_environment_runtime_records_with_all_params(self, client: Gitpod) -> None:
+        usage = client.usage.list_environment_runtime_records(
+            token="token",
+            page_size=0,
+            filter={
+                "date_range": {
+                    "end_time": parse_datetime("2024-01-02T00:00:00Z"),
+                    "start_time": parse_datetime("2024-01-01T00:00:00Z"),
+                },
+                "project_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            },
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(SyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_environment_runtime_records(self, client: Gitpod) -> None:
+        response = client.usage.with_raw_response.list_environment_runtime_records()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        usage = response.parse()
+        assert_matches_type(SyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_environment_runtime_records(self, client: Gitpod) -> None:
+        with client.usage.with_streaming_response.list_environment_runtime_records() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            usage = response.parse()
+            assert_matches_type(SyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncUsage:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_environment_runtime_records(self, async_client: AsyncGitpod) -> None:
+        usage = await async_client.usage.list_environment_runtime_records()
+        assert_matches_type(AsyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_environment_runtime_records_with_all_params(self, async_client: AsyncGitpod) -> None:
+        usage = await async_client.usage.list_environment_runtime_records(
+            token="token",
+            page_size=0,
+            filter={
+                "date_range": {
+                    "end_time": parse_datetime("2024-01-02T00:00:00Z"),
+                    "start_time": parse_datetime("2024-01-01T00:00:00Z"),
+                },
+                "project_id": "d2c94c27-3b76-4a42-b88c-95a85e392c68",
+            },
+            pagination={
+                "token": "token",
+                "page_size": 100,
+            },
+        )
+        assert_matches_type(AsyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_environment_runtime_records(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.usage.with_raw_response.list_environment_runtime_records()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        usage = await response.parse()
+        assert_matches_type(AsyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_environment_runtime_records(self, async_client: AsyncGitpod) -> None:
+        async with async_client.usage.with_streaming_response.list_environment_runtime_records() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            usage = await response.parse()
+            assert_matches_type(AsyncRecordsPage[EnvironmentUsageRecord], usage, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py
new file mode 100644
index 0000000..2c16986
--- /dev/null
+++ b/tests/api_resources/test_users.py
@@ -0,0 +1,170 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types import UserGetAuthenticatedUserResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestUsers:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_authenticated_user(self, client: Gitpod) -> None:
+        user = client.users.get_authenticated_user()
+        assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_authenticated_user_with_all_params(self, client: Gitpod) -> None:
+        user = client.users.get_authenticated_user(
+            empty=True,
+        )
+        assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get_authenticated_user(self, client: Gitpod) -> None:
+        response = client.users.with_raw_response.get_authenticated_user()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        user = response.parse()
+        assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get_authenticated_user(self, client: Gitpod) -> None:
+        with client.users.with_streaming_response.get_authenticated_user() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            user = response.parse()
+            assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_set_suspended(self, client: Gitpod) -> None:
+        user = client.users.set_suspended()
+        assert_matches_type(object, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_set_suspended_with_all_params(self, client: Gitpod) -> None:
+        user = client.users.set_suspended(
+            suspended=False,
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_set_suspended(self, client: Gitpod) -> None:
+        response = client.users.with_raw_response.set_suspended()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        user = response.parse()
+        assert_matches_type(object, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_set_suspended(self, client: Gitpod) -> None:
+        with client.users.with_streaming_response.set_suspended() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            user = response.parse()
+            assert_matches_type(object, user, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncUsers:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_authenticated_user(self, async_client: AsyncGitpod) -> None:
+        user = await async_client.users.get_authenticated_user()
+        assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_authenticated_user_with_all_params(self, async_client: AsyncGitpod) -> None:
+        user = await async_client.users.get_authenticated_user(
+            empty=True,
+        )
+        assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get_authenticated_user(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.users.with_raw_response.get_authenticated_user()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        user = await response.parse()
+        assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get_authenticated_user(self, async_client: AsyncGitpod) -> None:
+        async with async_client.users.with_streaming_response.get_authenticated_user() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            user = await response.parse()
+            assert_matches_type(UserGetAuthenticatedUserResponse, user, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_set_suspended(self, async_client: AsyncGitpod) -> None:
+        user = await async_client.users.set_suspended()
+        assert_matches_type(object, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_set_suspended_with_all_params(self, async_client: AsyncGitpod) -> None:
+        user = await async_client.users.set_suspended(
+            suspended=False,
+            user_id="f53d2330-3795-4c5d-a1f3-453121af9c60",
+        )
+        assert_matches_type(object, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_set_suspended(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.users.with_raw_response.set_suspended()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        user = await response.parse()
+        assert_matches_type(object, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_set_suspended(self, async_client: AsyncGitpod) -> None:
+        async with async_client.users.with_streaming_response.set_suspended() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            user = await response.parse()
+            assert_matches_type(object, user, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/__init__.py b/tests/api_resources/users/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/users/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/users/test_dotfiles.py b/tests/api_resources/users/test_dotfiles.py
new file mode 100644
index 0000000..1bb65b7
--- /dev/null
+++ b/tests/api_resources/users/test_dotfiles.py
@@ -0,0 +1,168 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.types.users import DotfileGetResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestDotfiles:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get(self, client: Gitpod) -> None:
+        dotfile = client.users.dotfiles.get()
+        assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_with_all_params(self, client: Gitpod) -> None:
+        dotfile = client.users.dotfiles.get(
+            empty=True,
+        )
+        assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get(self, client: Gitpod) -> None:
+        response = client.users.dotfiles.with_raw_response.get()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        dotfile = response.parse()
+        assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get(self, client: Gitpod) -> None:
+        with client.users.dotfiles.with_streaming_response.get() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            dotfile = response.parse()
+            assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_set(self, client: Gitpod) -> None:
+        dotfile = client.users.dotfiles.set()
+        assert_matches_type(object, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_set_with_all_params(self, client: Gitpod) -> None:
+        dotfile = client.users.dotfiles.set(
+            repository="https://example.com",
+        )
+        assert_matches_type(object, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_set(self, client: Gitpod) -> None:
+        response = client.users.dotfiles.with_raw_response.set()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        dotfile = response.parse()
+        assert_matches_type(object, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_set(self, client: Gitpod) -> None:
+        with client.users.dotfiles.with_streaming_response.set() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            dotfile = response.parse()
+            assert_matches_type(object, dotfile, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncDotfiles:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get(self, async_client: AsyncGitpod) -> None:
+        dotfile = await async_client.users.dotfiles.get()
+        assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_with_all_params(self, async_client: AsyncGitpod) -> None:
+        dotfile = await async_client.users.dotfiles.get(
+            empty=True,
+        )
+        assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.users.dotfiles.with_raw_response.get()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        dotfile = await response.parse()
+        assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get(self, async_client: AsyncGitpod) -> None:
+        async with async_client.users.dotfiles.with_streaming_response.get() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            dotfile = await response.parse()
+            assert_matches_type(DotfileGetResponse, dotfile, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_set(self, async_client: AsyncGitpod) -> None:
+        dotfile = await async_client.users.dotfiles.set()
+        assert_matches_type(object, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_set_with_all_params(self, async_client: AsyncGitpod) -> None:
+        dotfile = await async_client.users.dotfiles.set(
+            repository="https://example.com",
+        )
+        assert_matches_type(object, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_set(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.users.dotfiles.with_raw_response.set()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        dotfile = await response.parse()
+        assert_matches_type(object, dotfile, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_set(self, async_client: AsyncGitpod) -> None:
+        async with async_client.users.dotfiles.with_streaming_response.set() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            dotfile = await response.parse()
+            assert_matches_type(object, dotfile, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/test_pats.py b/tests/api_resources/users/test_pats.py
new file mode 100644
index 0000000..c8e1082
--- /dev/null
+++ b/tests/api_resources/users/test_pats.py
@@ -0,0 +1,253 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from tests.utils import assert_matches_type
+from gitpod.pagination import SyncPersonalAccessTokensPage, AsyncPersonalAccessTokensPage
+from gitpod.types.users import PatGetResponse, PersonalAccessToken
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPats:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Gitpod) -> None:
+        pat = client.users.pats.list()
+        assert_matches_type(SyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Gitpod) -> None:
+        pat = client.users.pats.list(
+            token="token",
+            page_size=0,
+            filter={"user_ids": ["f53d2330-3795-4c5d-a1f3-453121af9c60"]},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(SyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Gitpod) -> None:
+        response = client.users.pats.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        pat = response.parse()
+        assert_matches_type(SyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Gitpod) -> None:
+        with client.users.pats.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            pat = response.parse()
+            assert_matches_type(SyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Gitpod) -> None:
+        pat = client.users.pats.delete()
+        assert_matches_type(object, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete_with_all_params(self, client: Gitpod) -> None:
+        pat = client.users.pats.delete(
+            personal_access_token_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Gitpod) -> None:
+        response = client.users.pats.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        pat = response.parse()
+        assert_matches_type(object, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Gitpod) -> None:
+        with client.users.pats.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            pat = response.parse()
+            assert_matches_type(object, pat, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get(self, client: Gitpod) -> None:
+        pat = client.users.pats.get()
+        assert_matches_type(PatGetResponse, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_with_all_params(self, client: Gitpod) -> None:
+        pat = client.users.pats.get(
+            personal_access_token_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(PatGetResponse, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get(self, client: Gitpod) -> None:
+        response = client.users.pats.with_raw_response.get()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        pat = response.parse()
+        assert_matches_type(PatGetResponse, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get(self, client: Gitpod) -> None:
+        with client.users.pats.with_streaming_response.get() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            pat = response.parse()
+            assert_matches_type(PatGetResponse, pat, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncPats:
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncGitpod) -> None:
+        pat = await async_client.users.pats.list()
+        assert_matches_type(AsyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncGitpod) -> None:
+        pat = await async_client.users.pats.list(
+            token="token",
+            page_size=0,
+            filter={"user_ids": ["f53d2330-3795-4c5d-a1f3-453121af9c60"]},
+            pagination={
+                "token": "token",
+                "page_size": 20,
+            },
+        )
+        assert_matches_type(AsyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.users.pats.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        pat = await response.parse()
+        assert_matches_type(AsyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncGitpod) -> None:
+        async with async_client.users.pats.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            pat = await response.parse()
+            assert_matches_type(AsyncPersonalAccessTokensPage[PersonalAccessToken], pat, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncGitpod) -> None:
+        pat = await async_client.users.pats.delete()
+        assert_matches_type(object, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete_with_all_params(self, async_client: AsyncGitpod) -> None:
+        pat = await async_client.users.pats.delete(
+            personal_access_token_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(object, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.users.pats.with_raw_response.delete()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        pat = await response.parse()
+        assert_matches_type(object, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncGitpod) -> None:
+        async with async_client.users.pats.with_streaming_response.delete() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            pat = await response.parse()
+            assert_matches_type(object, pat, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get(self, async_client: AsyncGitpod) -> None:
+        pat = await async_client.users.pats.get()
+        assert_matches_type(PatGetResponse, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_with_all_params(self, async_client: AsyncGitpod) -> None:
+        pat = await async_client.users.pats.get(
+            personal_access_token_id="d2c94c27-3b76-4a42-b88c-95a85e392c68",
+        )
+        assert_matches_type(PatGetResponse, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get(self, async_client: AsyncGitpod) -> None:
+        response = await async_client.users.pats.with_raw_response.get()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        pat = await response.parse()
+        assert_matches_type(PatGetResponse, pat, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get(self, async_client: AsyncGitpod) -> None:
+        async with async_client.users.pats.with_streaming_response.get() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            pat = await response.parse()
+            assert_matches_type(PatGetResponse, pat, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..8697828
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+import logging
+from typing import TYPE_CHECKING, Iterator, AsyncIterator
+
+import httpx
+import pytest
+from pytest_asyncio import is_async_test
+
+from gitpod import Gitpod, AsyncGitpod, DefaultAioHttpClient
+from gitpod._utils import is_dict
+
+if TYPE_CHECKING:
+    from _pytest.fixtures import FixtureRequest  # pyright: ignore[reportPrivateImportUsage]
+
+pytest.register_assert_rewrite("tests.utils")
+
+logging.getLogger("gitpod").setLevel(logging.DEBUG)
+
+
+# automatically add `pytest.mark.asyncio()` to all of our async tests
+# so we don't have to add that boilerplate everywhere
+def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
+    pytest_asyncio_tests = (item for item in items if is_async_test(item))
+    session_scope_marker = pytest.mark.asyncio(loop_scope="session")
+    for async_test in pytest_asyncio_tests:
+        async_test.add_marker(session_scope_marker, append=False)
+
+    # We skip tests that use both the aiohttp client and respx_mock as respx_mock
+    # doesn't support custom transports.
+    for item in items:
+        if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames:
+            continue
+
+        if not hasattr(item, "callspec"):
+            continue
+
+        async_client_param = item.callspec.params.get("async_client")
+        if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp":
+            item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock"))
+
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+bearer_token = "My Bearer Token"
+
+
+@pytest.fixture(scope="session")
+def client(request: FixtureRequest) -> Iterator[Gitpod]:
+    strict = getattr(request, "param", True)
+    if not isinstance(strict, bool):
+        raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
+
+    with Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict) as client:
+        yield client
+
+
+@pytest.fixture(scope="session")
+async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncGitpod]:
+    param = getattr(request, "param", True)
+
+    # defaults
+    strict = True
+    http_client: None | httpx.AsyncClient = None
+
+    if isinstance(param, bool):
+        strict = param
+    elif is_dict(param):
+        strict = param.get("strict", True)
+        assert isinstance(strict, bool)
+
+        http_client_type = param.get("http_client", "httpx")
+        if http_client_type == "aiohttp":
+            http_client = DefaultAioHttpClient()
+    else:
+        raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
+
+    async with AsyncGitpod(
+        base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict, http_client=http_client
+    ) as client:
+        yield client
diff --git a/tests/decoders/test_jsonl.py b/tests/decoders/test_jsonl.py
new file mode 100644
index 0000000..a14a93e
--- /dev/null
+++ b/tests/decoders/test_jsonl.py
@@ -0,0 +1,88 @@
+from __future__ import annotations
+
+from typing import Any, Iterator, AsyncIterator
+from typing_extensions import TypeVar
+
+import httpx
+import pytest
+
+from gitpod._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder
+
+_T = TypeVar("_T")
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_basic(sync: bool) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'{"foo":true}\n'
+        yield b'{"bar":false}\n'
+
+    iterator = make_jsonl_iterator(
+        content=body(),
+        sync=sync,
+        line_type=object,
+    )
+
+    assert await iter_next(iterator) == {"foo": True}
+    assert await iter_next(iterator) == {"bar": False}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_new_lines_in_json(
+    sync: bool,
+) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'{"content":"Hello, world!\\nHow are you doing?"}'
+
+    iterator = make_jsonl_iterator(content=body(), sync=sync, line_type=object)
+
+    assert await iter_next(iterator) == {"content": "Hello, world!\nHow are you doing?"}
+
+
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multi_byte_character_multiple_chunks(
+    sync: bool,
+) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'{"content":"'
+        # bytes taken from the string 'известни' and arbitrarily split
+        # so that some multi-byte characters span multiple chunks
+        yield b"\xd0"
+        yield b"\xb8\xd0\xb7\xd0"
+        yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8"
+        yield b'"}\n'
+
+    iterator = make_jsonl_iterator(content=body(), sync=sync, line_type=object)
+
+    assert await iter_next(iterator) == {"content": "известни"}
+
+
+async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]:
+    for chunk in iter:
+        yield chunk
+
+
+async def iter_next(iter: Iterator[_T] | AsyncIterator[_T]) -> _T:
+    if isinstance(iter, AsyncIterator):
+        return await iter.__anext__()
+    return next(iter)
+
+
+async def assert_empty_iter(decoder: JSONLDecoder[Any] | AsyncJSONLDecoder[Any]) -> None:
+    with pytest.raises((StopAsyncIteration, RuntimeError)):
+        await iter_next(decoder)
+
+
+def make_jsonl_iterator(
+    content: Iterator[bytes],
+    *,
+    sync: bool,
+    line_type: type[_T],
+) -> JSONLDecoder[_T] | AsyncJSONLDecoder[_T]:
+    if sync:
+        return JSONLDecoder(line_type=line_type, raw_iterator=content, http_response=httpx.Response(200))
+
+    return AsyncJSONLDecoder(line_type=line_type, raw_iterator=to_aiter(content), http_response=httpx.Response(200))
diff --git a/tests/sample_file.txt b/tests/sample_file.txt
new file mode 100644
index 0000000..af5626b
--- /dev/null
+++ b/tests/sample_file.txt
@@ -0,0 +1 @@
+Hello, world!
diff --git a/tests/test_client.py b/tests/test_client.py
new file mode 100644
index 0000000..1c80f7b
--- /dev/null
+++ b/tests/test_client.py
@@ -0,0 +1,1778 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import gc
+import os
+import sys
+import json
+import time
+import asyncio
+import inspect
+import subprocess
+import tracemalloc
+from typing import Any, Union, cast
+from textwrap import dedent
+from unittest import mock
+from typing_extensions import Literal
+
+import httpx
+import pytest
+from respx import MockRouter
+from pydantic import ValidationError
+
+from gitpod import Gitpod, AsyncGitpod, APIResponseValidationError
+from gitpod._types import Omit
+from gitpod._models import BaseModel, FinalRequestOptions
+from gitpod._exceptions import GitpodError, APIStatusError, APITimeoutError, APIResponseValidationError
+from gitpod._base_client import (
+    DEFAULT_TIMEOUT,
+    HTTPX_DEFAULT_TIMEOUT,
+    BaseClient,
+    DefaultHttpxClient,
+    DefaultAsyncHttpxClient,
+    make_request_options,
+)
+
+from .utils import update_env
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+bearer_token = "My Bearer Token"
+
+
+def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]:
+    request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+    url = httpx.URL(request.url)
+    return dict(url.params)
+
+
+def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
+    return 0.1
+
+
+def _get_open_connections(client: Gitpod | AsyncGitpod) -> int:
+    transport = client._client._transport
+    assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
+
+    pool = transport._pool
+    return len(pool._requests)
+
+
+class TestGitpod:
+    client = Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_raw_response(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(
+            return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
+        )
+
+        response = self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    def test_copy(self) -> None:
+        copied = self.client.copy()
+        assert id(copied) != id(self.client)
+
+        copied = self.client.copy(bearer_token="another My Bearer Token")
+        assert copied.bearer_token == "another My Bearer Token"
+        assert self.client.bearer_token == "My Bearer Token"
+
+    def test_copy_default_options(self) -> None:
+        # options that have a default are overridden correctly
+        copied = self.client.copy(max_retries=7)
+        assert copied.max_retries == 7
+        assert self.client.max_retries == 2
+
+        copied2 = copied.copy(max_retries=6)
+        assert copied2.max_retries == 6
+        assert copied.max_retries == 7
+
+        # timeout
+        assert isinstance(self.client.timeout, httpx.Timeout)
+        copied = self.client.copy(timeout=None)
+        assert copied.timeout is None
+        assert isinstance(self.client.timeout, httpx.Timeout)
+
+    def test_copy_default_headers(self) -> None:
+        client = Gitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        assert client.default_headers["X-Foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert copied.default_headers["X-Foo"] == "bar"
+
+        # merges already given headers
+        copied = client.copy(default_headers={"X-Bar": "stainless"})
+        assert copied.default_headers["X-Foo"] == "bar"
+        assert copied.default_headers["X-Bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_headers={"X-Foo": "stainless"})
+        assert copied.default_headers["X-Foo"] == "stainless"
+
+        # set_default_headers
+
+        # completely overrides already set values
+        copied = client.copy(set_default_headers={})
+        assert copied.default_headers.get("X-Foo") is None
+
+        copied = client.copy(set_default_headers={"X-Bar": "Robert"})
+        assert copied.default_headers["X-Bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+
+    def test_copy_default_query(self) -> None:
+        client = Gitpod(
+            base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"}
+        )
+        assert _get_params(client)["foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert _get_params(copied)["foo"] == "bar"
+
+        # merges already given params
+        copied = client.copy(default_query={"bar": "stainless"})
+        params = _get_params(copied)
+        assert params["foo"] == "bar"
+        assert params["bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_query={"foo": "stainless"})
+        assert _get_params(copied)["foo"] == "stainless"
+
+        # set_default_query
+
+        # completely overrides already set values
+        copied = client.copy(set_default_query={})
+        assert _get_params(copied) == {}
+
+        copied = client.copy(set_default_query={"bar": "Robert"})
+        assert _get_params(copied)["bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            # TODO: update
+            match="`default_query` and `set_default_query` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_query={}, default_query={"foo": "Bar"})
+
+    def test_copy_signature(self) -> None:
+        # ensure the same parameters that can be passed to the client are defined in the `.copy()` method
+        init_signature = inspect.signature(
+            # mypy doesn't like that we access the `__init__` property.
+            self.client.__init__,  # type: ignore[misc]
+        )
+        copy_signature = inspect.signature(self.client.copy)
+        exclude_params = {"transport", "proxies", "_strict_response_validation"}
+
+        for name in init_signature.parameters.keys():
+            if name in exclude_params:
+                continue
+
+            copy_param = copy_signature.parameters.get(name)
+            assert copy_param is not None, f"copy() signature is missing the {name} param"
+
+    @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
+    def test_copy_build_request(self) -> None:
+        options = FinalRequestOptions(method="get", url="/foo")
+
+        def build_request(options: FinalRequestOptions) -> None:
+            client = self.client.copy()
+            client._build_request(options)
+
+        # ensure that the machinery is warmed up before tracing starts.
+        build_request(options)
+        gc.collect()
+
+        tracemalloc.start(1000)
+
+        snapshot_before = tracemalloc.take_snapshot()
+
+        ITERATIONS = 10
+        for _ in range(ITERATIONS):
+            build_request(options)
+
+        gc.collect()
+        snapshot_after = tracemalloc.take_snapshot()
+
+        tracemalloc.stop()
+
+        def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
+            if diff.count == 0:
+                # Avoid false positives by considering only leaks (i.e. allocations that persist).
+                return
+
+            if diff.count % ITERATIONS != 0:
+                # Avoid false positives by considering only leaks that appear per iteration.
+                return
+
+            for frame in diff.traceback:
+                if any(
+                    frame.filename.endswith(fragment)
+                    for fragment in [
+                        # to_raw_response_wrapper leaks through the @functools.wraps() decorator.
+                        #
+                        # removing the decorator fixes the leak for reasons we don't understand.
+                        "gitpod/_legacy_response.py",
+                        "gitpod/_response.py",
+                        # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason.
+                        "gitpod/_compat.py",
+                        # Standard library leaks we don't care about.
+                        "/logging/__init__.py",
+                    ]
+                ):
+                    return
+
+            leaks.append(diff)
+
+        leaks: list[tracemalloc.StatisticDiff] = []
+        for diff in snapshot_after.compare_to(snapshot_before, "traceback"):
+            add_leak(leaks, diff)
+        if leaks:
+            for leak in leaks:
+                print("MEMORY LEAK:", leak)
+                for frame in leak.traceback:
+                    print(frame)
+            raise AssertionError()
+
+    def test_request_timeout(self) -> None:
+        request = self.client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == DEFAULT_TIMEOUT
+
+        request = self.client._build_request(
+            FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
+        )
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(100.0)
+
+    def test_client_timeout_option(self) -> None:
+        client = Gitpod(
+            base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0)
+        )
+
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(0)
+
+    def test_http_client_timeout_option(self) -> None:
+        # custom timeout given to the httpx client should be used
+        with httpx.Client(timeout=None) as http_client:
+            client = Gitpod(
+                base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == httpx.Timeout(None)
+
+        # no timeout given to the httpx client should not use the httpx default
+        with httpx.Client() as http_client:
+            client = Gitpod(
+                base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT
+
+        # explicitly passing the default timeout currently results in it being ignored
+        with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
+            client = Gitpod(
+                base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT  # our default
+
+    async def test_invalid_http_client(self) -> None:
+        with pytest.raises(TypeError, match="Invalid `http_client` arg"):
+            async with httpx.AsyncClient() as http_client:
+                Gitpod(
+                    base_url=base_url,
+                    bearer_token=bearer_token,
+                    _strict_response_validation=True,
+                    http_client=cast(Any, http_client),
+                )
+
+    def test_default_headers_option(self) -> None:
+        client = Gitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "bar"
+        assert request.headers.get("x-stainless-lang") == "python"
+
+        client2 = Gitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_headers={
+                "X-Foo": "stainless",
+                "X-Stainless-Lang": "my-overriding-header",
+            },
+        )
+        request = client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "stainless"
+        assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+
+    def test_validate_headers(self) -> None:
+        client = Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("Authorization") == f"Bearer {bearer_token}"
+
+        with pytest.raises(GitpodError):
+            with update_env(**{"GITPOD_API_KEY": Omit()}):
+                client2 = Gitpod(base_url=base_url, bearer_token=None, _strict_response_validation=True)
+            _ = client2
+
+    def test_default_query_option(self) -> None:
+        client = Gitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_query={"query_param": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"query_param": "bar"}
+
+        request = client._build_request(
+            FinalRequestOptions(
+                method="get",
+                url="/foo",
+                params={"foo": "baz", "query_param": "overridden"},
+            )
+        )
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
+
+    def test_request_extra_json(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": False}
+
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"baz": False}
+
+        # `extra_json` takes priority over `json_data` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar", "baz": True},
+                extra_json={"baz": None},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": None}
+
+    def test_request_extra_headers(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(extra_headers={"X-Foo": "Foo"}),
+            ),
+        )
+        assert request.headers.get("X-Foo") == "Foo"
+
+        # `extra_headers` takes priority over `default_headers` when keys clash
+        request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_headers={"X-Bar": "false"},
+                ),
+            ),
+        )
+        assert request.headers.get("X-Bar") == "false"
+
+    def test_request_extra_query(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_query={"my_query_param": "Foo"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"my_query_param": "Foo"}
+
+        # if both `query` and `extra_query` are given, they are merged
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"bar": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"bar": "1", "foo": "2"}
+
+        # `extra_query` takes priority over `query` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"foo": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"foo": "2"}
+
+    def test_multipart_repeating_array(self, client: Gitpod) -> None:
+        request = client._build_request(
+            FinalRequestOptions.construct(
+                method="post",
+                url="/foo",
+                headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
+                json_data={"array": ["foo", "bar"]},
+                files=[("foo.txt", b"hello world")],
+            )
+        )
+
+        assert request.read().split(b"\r\n") == [
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"foo",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"bar",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="foo.txt"; filename="upload"',
+            b"Content-Type: application/octet-stream",
+            b"",
+            b"hello world",
+            b"--6b7ba517decee4a450543ea6ae821c82--",
+            b"",
+        ]
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_basic_union_response(self, respx_mock: MockRouter) -> None:
+        class Model1(BaseModel):
+            name: str
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_union_response_different_types(self, respx_mock: MockRouter) -> None:
+        """Union of objects with the same field name using a different type"""
+
+        class Model1(BaseModel):
+            foo: int
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
+
+        response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model1)
+        assert response.foo == 1
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None:
+        """
+        Response that sets Content-Type to something other than application/json but returns json data
+        """
+
+        class Model(BaseModel):
+            foo: int
+
+        respx_mock.get("/foo").mock(
+            return_value=httpx.Response(
+                200,
+                content=json.dumps({"foo": 2}),
+                headers={"Content-Type": "application/text"},
+            )
+        )
+
+        response = self.client.get("/foo", cast_to=Model)
+        assert isinstance(response, Model)
+        assert response.foo == 2
+
+    def test_base_url_setter(self) -> None:
+        client = Gitpod(
+            base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True
+        )
+        assert client.base_url == "https://example.com/from_init/"
+
+        client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
+
+        assert client.base_url == "https://example.com/from_setter/"
+
+    def test_base_url_env(self) -> None:
+        with update_env(GITPOD_BASE_URL="http://localhost:5000/from/env"):
+            client = Gitpod(bearer_token=bearer_token, _strict_response_validation=True)
+            assert client.base_url == "http://localhost:5000/from/env/"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            Gitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+            ),
+            Gitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                http_client=httpx.Client(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_trailing_slash(self, client: Gitpod) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            Gitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+            ),
+            Gitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                http_client=httpx.Client(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_no_trailing_slash(self, client: Gitpod) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            Gitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+            ),
+            Gitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                http_client=httpx.Client(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_absolute_request_url(self, client: Gitpod) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="https://myapi.com/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "https://myapi.com/foo"
+
+    def test_copied_client_does_not_close_http(self) -> None:
+        client = Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+        assert not client.is_closed()
+
+        copied = client.copy()
+        assert copied is not client
+
+        del copied
+
+        assert not client.is_closed()
+
+    def test_client_context_manager(self) -> None:
+        client = Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+        with client as c2:
+            assert c2 is client
+            assert not c2.is_closed()
+            assert not client.is_closed()
+        assert client.is_closed()
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_client_response_validation_error(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
+
+        with pytest.raises(APIResponseValidationError) as exc:
+            self.client.get("/foo", cast_to=Model)
+
+        assert isinstance(exc.value.__cause__, ValidationError)
+
+    def test_client_max_retries_validation(self) -> None:
+        with pytest.raises(TypeError, match=r"max_retries cannot be None"):
+            Gitpod(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                max_retries=cast(Any, None),
+            )
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            name: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
+
+        strict_client = Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+
+        with pytest.raises(APIResponseValidationError):
+            strict_client.get("/foo", cast_to=Model)
+
+        client = Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False)
+
+        response = client.get("/foo", cast_to=Model)
+        assert isinstance(response, str)  # type: ignore[unreachable]
+
+    @pytest.mark.parametrize(
+        "remaining_retries,retry_after,timeout",
+        [
+            [3, "20", 20],
+            [3, "0", 0.5],
+            [3, "-10", 0.5],
+            [3, "60", 60],
+            [3, "61", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
+            [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
+            [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
+            [3, "99999999999999999999999999999999999", 0.5],
+            [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "", 0.5],
+            [2, "", 0.5 * 2.0],
+            [1, "", 0.5 * 4.0],
+            [-1100, "", 8],  # test large number potentially overflowing
+        ],
+    )
+    @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
+    def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
+        client = Gitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+
+        headers = httpx.Headers({"retry-after": retry_after})
+        options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
+        calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
+        assert calculated == pytest.approx(timeout, 0.5 * 0.875)  # pyright: ignore[reportUnknownMemberType]
+
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Gitpod) -> None:
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(
+            side_effect=httpx.TimeoutException("Test timeout error")
+        )
+
+        with pytest.raises(APITimeoutError):
+            client.identity.with_streaming_response.get_authenticated_identity().__enter__()
+
+        assert _get_open_connections(self.client) == 0
+
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Gitpod) -> None:
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(return_value=httpx.Response(500))
+
+        with pytest.raises(APIStatusError):
+            client.identity.with_streaming_response.get_authenticated_identity().__enter__()
+        assert _get_open_connections(self.client) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.parametrize("failure_mode", ["status", "exception"])
+    def test_retries_taken(
+        self,
+        client: Gitpod,
+        failures_before_success: int,
+        failure_mode: Literal["status", "exception"],
+        respx_mock: MockRouter,
+    ) -> None:
+        client = client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                if failure_mode == "exception":
+                    raise RuntimeError("oops")
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(side_effect=retry_handler)
+
+        response = client.identity.with_raw_response.get_authenticated_identity()
+
+        assert response.retries_taken == failures_before_success
+        assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_omit_retry_count_header(
+        self, client: Gitpod, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
+        client = client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(side_effect=retry_handler)
+
+        response = client.identity.with_raw_response.get_authenticated_identity(
+            extra_headers={"x-stainless-retry-count": Omit()}
+        )
+
+        assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_overwrite_retry_count_header(
+        self, client: Gitpod, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
+        client = client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(side_effect=retry_handler)
+
+        response = client.identity.with_raw_response.get_authenticated_identity(
+            extra_headers={"x-stainless-retry-count": "42"}
+        )
+
+        assert response.http_request.headers.get("x-stainless-retry-count") == "42"
+
+    def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
+        # Test that the proxy environment variables are set correctly
+        monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+
+        client = DefaultHttpxClient()
+
+        mounts = tuple(client._mounts.items())
+        assert len(mounts) == 1
+        assert mounts[0][0].pattern == "https://"
+
+    @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
+    def test_default_client_creation(self) -> None:
+        # Ensure that the client can be initialized without any exceptions
+        DefaultHttpxClient(
+            verify=True,
+            cert=None,
+            trust_env=True,
+            http1=True,
+            http2=False,
+            limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
+        )
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_follow_redirects(self, respx_mock: MockRouter) -> None:
+        # Test that the default follow_redirects=True allows following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+        respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
+
+        response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert response.json() == {"status": "ok"}
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
+        # Test that follow_redirects=False prevents following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+
+        with pytest.raises(APIStatusError) as exc_info:
+            self.client.post(
+                "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
+            )
+
+        assert exc_info.value.response.status_code == 302
+        assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
+
+
+class TestAsyncGitpod:
+    client = AsyncGitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_raw_response(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = await self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(
+            return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
+        )
+
+        response = await self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    def test_copy(self) -> None:
+        copied = self.client.copy()
+        assert id(copied) != id(self.client)
+
+        copied = self.client.copy(bearer_token="another My Bearer Token")
+        assert copied.bearer_token == "another My Bearer Token"
+        assert self.client.bearer_token == "My Bearer Token"
+
+    def test_copy_default_options(self) -> None:
+        # options that have a default are overridden correctly
+        copied = self.client.copy(max_retries=7)
+        assert copied.max_retries == 7
+        assert self.client.max_retries == 2
+
+        copied2 = copied.copy(max_retries=6)
+        assert copied2.max_retries == 6
+        assert copied.max_retries == 7
+
+        # timeout
+        assert isinstance(self.client.timeout, httpx.Timeout)
+        copied = self.client.copy(timeout=None)
+        assert copied.timeout is None
+        assert isinstance(self.client.timeout, httpx.Timeout)
+
+    def test_copy_default_headers(self) -> None:
+        client = AsyncGitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        assert client.default_headers["X-Foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert copied.default_headers["X-Foo"] == "bar"
+
+        # merges already given headers
+        copied = client.copy(default_headers={"X-Bar": "stainless"})
+        assert copied.default_headers["X-Foo"] == "bar"
+        assert copied.default_headers["X-Bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_headers={"X-Foo": "stainless"})
+        assert copied.default_headers["X-Foo"] == "stainless"
+
+        # set_default_headers
+
+        # completely overrides already set values
+        copied = client.copy(set_default_headers={})
+        assert copied.default_headers.get("X-Foo") is None
+
+        copied = client.copy(set_default_headers={"X-Bar": "Robert"})
+        assert copied.default_headers["X-Bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+
+    def test_copy_default_query(self) -> None:
+        client = AsyncGitpod(
+            base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"}
+        )
+        assert _get_params(client)["foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert _get_params(copied)["foo"] == "bar"
+
+        # merges already given params
+        copied = client.copy(default_query={"bar": "stainless"})
+        params = _get_params(copied)
+        assert params["foo"] == "bar"
+        assert params["bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_query={"foo": "stainless"})
+        assert _get_params(copied)["foo"] == "stainless"
+
+        # set_default_query
+
+        # completely overrides already set values
+        copied = client.copy(set_default_query={})
+        assert _get_params(copied) == {}
+
+        copied = client.copy(set_default_query={"bar": "Robert"})
+        assert _get_params(copied)["bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            # TODO: update
+            match="`default_query` and `set_default_query` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_query={}, default_query={"foo": "Bar"})
+
+    def test_copy_signature(self) -> None:
+        # ensure the same parameters that can be passed to the client are defined in the `.copy()` method
+        init_signature = inspect.signature(
+            # mypy doesn't like that we access the `__init__` property.
+            self.client.__init__,  # type: ignore[misc]
+        )
+        copy_signature = inspect.signature(self.client.copy)
+        exclude_params = {"transport", "proxies", "_strict_response_validation"}
+
+        for name in init_signature.parameters.keys():
+            if name in exclude_params:
+                continue
+
+            copy_param = copy_signature.parameters.get(name)
+            assert copy_param is not None, f"copy() signature is missing the {name} param"
+
+    @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
+    def test_copy_build_request(self) -> None:
+        options = FinalRequestOptions(method="get", url="/foo")
+
+        def build_request(options: FinalRequestOptions) -> None:
+            client = self.client.copy()
+            client._build_request(options)
+
+        # ensure that the machinery is warmed up before tracing starts.
+        build_request(options)
+        gc.collect()
+
+        tracemalloc.start(1000)
+
+        snapshot_before = tracemalloc.take_snapshot()
+
+        ITERATIONS = 10
+        for _ in range(ITERATIONS):
+            build_request(options)
+
+        gc.collect()
+        snapshot_after = tracemalloc.take_snapshot()
+
+        tracemalloc.stop()
+
+        def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
+            if diff.count == 0:
+                # Avoid false positives by considering only leaks (i.e. allocations that persist).
+                return
+
+            if diff.count % ITERATIONS != 0:
+                # Avoid false positives by considering only leaks that appear per iteration.
+                return
+
+            for frame in diff.traceback:
+                if any(
+                    frame.filename.endswith(fragment)
+                    for fragment in [
+                        # to_raw_response_wrapper leaks through the @functools.wraps() decorator.
+                        #
+                        # removing the decorator fixes the leak for reasons we don't understand.
+                        "gitpod/_legacy_response.py",
+                        "gitpod/_response.py",
+                        # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason.
+                        "gitpod/_compat.py",
+                        # Standard library leaks we don't care about.
+                        "/logging/__init__.py",
+                    ]
+                ):
+                    return
+
+            leaks.append(diff)
+
+        leaks: list[tracemalloc.StatisticDiff] = []
+        for diff in snapshot_after.compare_to(snapshot_before, "traceback"):
+            add_leak(leaks, diff)
+        if leaks:
+            for leak in leaks:
+                print("MEMORY LEAK:", leak)
+                for frame in leak.traceback:
+                    print(frame)
+            raise AssertionError()
+
+    async def test_request_timeout(self) -> None:
+        request = self.client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == DEFAULT_TIMEOUT
+
+        request = self.client._build_request(
+            FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
+        )
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(100.0)
+
+    async def test_client_timeout_option(self) -> None:
+        client = AsyncGitpod(
+            base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0)
+        )
+
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(0)
+
+    async def test_http_client_timeout_option(self) -> None:
+        # custom timeout given to the httpx client should be used
+        async with httpx.AsyncClient(timeout=None) as http_client:
+            client = AsyncGitpod(
+                base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == httpx.Timeout(None)
+
+        # no timeout given to the httpx client should not use the httpx default
+        async with httpx.AsyncClient() as http_client:
+            client = AsyncGitpod(
+                base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT
+
+        # explicitly passing the default timeout currently results in it being ignored
+        async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
+            client = AsyncGitpod(
+                base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT  # our default
+
+    def test_invalid_http_client(self) -> None:
+        with pytest.raises(TypeError, match="Invalid `http_client` arg"):
+            with httpx.Client() as http_client:
+                AsyncGitpod(
+                    base_url=base_url,
+                    bearer_token=bearer_token,
+                    _strict_response_validation=True,
+                    http_client=cast(Any, http_client),
+                )
+
+    def test_default_headers_option(self) -> None:
+        client = AsyncGitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "bar"
+        assert request.headers.get("x-stainless-lang") == "python"
+
+        client2 = AsyncGitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_headers={
+                "X-Foo": "stainless",
+                "X-Stainless-Lang": "my-overriding-header",
+            },
+        )
+        request = client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "stainless"
+        assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+
+    def test_validate_headers(self) -> None:
+        client = AsyncGitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("Authorization") == f"Bearer {bearer_token}"
+
+        with pytest.raises(GitpodError):
+            with update_env(**{"GITPOD_API_KEY": Omit()}):
+                client2 = AsyncGitpod(base_url=base_url, bearer_token=None, _strict_response_validation=True)
+            _ = client2
+
+    def test_default_query_option(self) -> None:
+        client = AsyncGitpod(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            _strict_response_validation=True,
+            default_query={"query_param": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"query_param": "bar"}
+
+        request = client._build_request(
+            FinalRequestOptions(
+                method="get",
+                url="/foo",
+                params={"foo": "baz", "query_param": "overridden"},
+            )
+        )
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
+
+    def test_request_extra_json(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": False}
+
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"baz": False}
+
+        # `extra_json` takes priority over `json_data` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar", "baz": True},
+                extra_json={"baz": None},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": None}
+
+    def test_request_extra_headers(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(extra_headers={"X-Foo": "Foo"}),
+            ),
+        )
+        assert request.headers.get("X-Foo") == "Foo"
+
+        # `extra_headers` takes priority over `default_headers` when keys clash
+        request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_headers={"X-Bar": "false"},
+                ),
+            ),
+        )
+        assert request.headers.get("X-Bar") == "false"
+
+    def test_request_extra_query(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_query={"my_query_param": "Foo"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"my_query_param": "Foo"}
+
+        # if both `query` and `extra_query` are given, they are merged
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"bar": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"bar": "1", "foo": "2"}
+
+        # `extra_query` takes priority over `query` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"foo": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"foo": "2"}
+
+    def test_multipart_repeating_array(self, async_client: AsyncGitpod) -> None:
+        request = async_client._build_request(
+            FinalRequestOptions.construct(
+                method="post",
+                url="/foo",
+                headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
+                json_data={"array": ["foo", "bar"]},
+                files=[("foo.txt", b"hello world")],
+            )
+        )
+
+        assert request.read().split(b"\r\n") == [
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"foo",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"bar",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="foo.txt"; filename="upload"',
+            b"Content-Type: application/octet-stream",
+            b"",
+            b"hello world",
+            b"--6b7ba517decee4a450543ea6ae821c82--",
+            b"",
+        ]
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_basic_union_response(self, respx_mock: MockRouter) -> None:
+        class Model1(BaseModel):
+            name: str
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_union_response_different_types(self, respx_mock: MockRouter) -> None:
+        """Union of objects with the same field name using a different type"""
+
+        class Model1(BaseModel):
+            foo: int
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
+
+        response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model1)
+        assert response.foo == 1
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None:
+        """
+        Response that sets Content-Type to something other than application/json but returns json data
+        """
+
+        class Model(BaseModel):
+            foo: int
+
+        respx_mock.get("/foo").mock(
+            return_value=httpx.Response(
+                200,
+                content=json.dumps({"foo": 2}),
+                headers={"Content-Type": "application/text"},
+            )
+        )
+
+        response = await self.client.get("/foo", cast_to=Model)
+        assert isinstance(response, Model)
+        assert response.foo == 2
+
+    def test_base_url_setter(self) -> None:
+        client = AsyncGitpod(
+            base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True
+        )
+        assert client.base_url == "https://example.com/from_init/"
+
+        client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
+
+        assert client.base_url == "https://example.com/from_setter/"
+
+    def test_base_url_env(self) -> None:
+        with update_env(GITPOD_BASE_URL="http://localhost:5000/from/env"):
+            client = AsyncGitpod(bearer_token=bearer_token, _strict_response_validation=True)
+            assert client.base_url == "http://localhost:5000/from/env/"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            AsyncGitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+            ),
+            AsyncGitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                http_client=httpx.AsyncClient(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_trailing_slash(self, client: AsyncGitpod) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            AsyncGitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+            ),
+            AsyncGitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                http_client=httpx.AsyncClient(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_no_trailing_slash(self, client: AsyncGitpod) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            AsyncGitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+            ),
+            AsyncGitpod(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                http_client=httpx.AsyncClient(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_absolute_request_url(self, client: AsyncGitpod) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="https://myapi.com/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "https://myapi.com/foo"
+
+    async def test_copied_client_does_not_close_http(self) -> None:
+        client = AsyncGitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+        assert not client.is_closed()
+
+        copied = client.copy()
+        assert copied is not client
+
+        del copied
+
+        await asyncio.sleep(0.2)
+        assert not client.is_closed()
+
+    async def test_client_context_manager(self) -> None:
+        client = AsyncGitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+        async with client as c2:
+            assert c2 is client
+            assert not c2.is_closed()
+            assert not client.is_closed()
+        assert client.is_closed()
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
+
+        with pytest.raises(APIResponseValidationError) as exc:
+            await self.client.get("/foo", cast_to=Model)
+
+        assert isinstance(exc.value.__cause__, ValidationError)
+
+    async def test_client_max_retries_validation(self) -> None:
+        with pytest.raises(TypeError, match=r"max_retries cannot be None"):
+            AsyncGitpod(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                _strict_response_validation=True,
+                max_retries=cast(Any, None),
+            )
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            name: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
+
+        strict_client = AsyncGitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+
+        with pytest.raises(APIResponseValidationError):
+            await strict_client.get("/foo", cast_to=Model)
+
+        client = AsyncGitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False)
+
+        response = await client.get("/foo", cast_to=Model)
+        assert isinstance(response, str)  # type: ignore[unreachable]
+
+    @pytest.mark.parametrize(
+        "remaining_retries,retry_after,timeout",
+        [
+            [3, "20", 20],
+            [3, "0", 0.5],
+            [3, "-10", 0.5],
+            [3, "60", 60],
+            [3, "61", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
+            [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
+            [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
+            [3, "99999999999999999999999999999999999", 0.5],
+            [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "", 0.5],
+            [2, "", 0.5 * 2.0],
+            [1, "", 0.5 * 4.0],
+            [-1100, "", 8],  # test large number potentially overflowing
+        ],
+    )
+    @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
+    @pytest.mark.asyncio
+    async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
+        client = AsyncGitpod(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
+
+        headers = httpx.Headers({"retry-after": retry_after})
+        options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
+        calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
+        assert calculated == pytest.approx(timeout, 0.5 * 0.875)  # pyright: ignore[reportUnknownMemberType]
+
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncGitpod) -> None:
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(
+            side_effect=httpx.TimeoutException("Test timeout error")
+        )
+
+        with pytest.raises(APITimeoutError):
+            await async_client.identity.with_streaming_response.get_authenticated_identity().__aenter__()
+
+        assert _get_open_connections(self.client) == 0
+
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncGitpod) -> None:
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(return_value=httpx.Response(500))
+
+        with pytest.raises(APIStatusError):
+            await async_client.identity.with_streaming_response.get_authenticated_identity().__aenter__()
+        assert _get_open_connections(self.client) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    @pytest.mark.parametrize("failure_mode", ["status", "exception"])
+    async def test_retries_taken(
+        self,
+        async_client: AsyncGitpod,
+        failures_before_success: int,
+        failure_mode: Literal["status", "exception"],
+        respx_mock: MockRouter,
+    ) -> None:
+        client = async_client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                if failure_mode == "exception":
+                    raise RuntimeError("oops")
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(side_effect=retry_handler)
+
+        response = await client.identity.with_raw_response.get_authenticated_identity()
+
+        assert response.retries_taken == failures_before_success
+        assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_omit_retry_count_header(
+        self, async_client: AsyncGitpod, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
+        client = async_client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(side_effect=retry_handler)
+
+        response = await client.identity.with_raw_response.get_authenticated_identity(
+            extra_headers={"x-stainless-retry-count": Omit()}
+        )
+
+        assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("gitpod._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_overwrite_retry_count_header(
+        self, async_client: AsyncGitpod, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
+        client = async_client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/gitpod.v1.IdentityService/GetAuthenticatedIdentity").mock(side_effect=retry_handler)
+
+        response = await client.identity.with_raw_response.get_authenticated_identity(
+            extra_headers={"x-stainless-retry-count": "42"}
+        )
+
+        assert response.http_request.headers.get("x-stainless-retry-count") == "42"
+
+    def test_get_platform(self) -> None:
+        # A previous implementation of asyncify could leave threads unterminated when
+        # used with nest_asyncio.
+        #
+        # Since nest_asyncio.apply() is global and cannot be un-applied, this
+        # test is run in a separate process to avoid affecting other tests.
+        test_code = dedent("""
+        import asyncio
+        import nest_asyncio
+        import threading
+
+        from gitpod._utils import asyncify
+        from gitpod._base_client import get_platform
+
+        async def test_main() -> None:
+            result = await asyncify(get_platform)()
+            print(result)
+            for thread in threading.enumerate():
+                print(thread.name)
+
+        nest_asyncio.apply()
+        asyncio.run(test_main())
+        """)
+        with subprocess.Popen(
+            [sys.executable, "-c", test_code],
+            text=True,
+        ) as process:
+            timeout = 10  # seconds
+
+            start_time = time.monotonic()
+            while True:
+                return_code = process.poll()
+                if return_code is not None:
+                    if return_code != 0:
+                        raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code")
+
+                    # success
+                    break
+
+                if time.monotonic() - start_time > timeout:
+                    process.kill()
+                    raise AssertionError("calling get_platform using asyncify resulted in a hung process")
+
+                time.sleep(0.1)
+
+    async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
+        # Test that the proxy environment variables are set correctly
+        monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+
+        client = DefaultAsyncHttpxClient()
+
+        mounts = tuple(client._mounts.items())
+        assert len(mounts) == 1
+        assert mounts[0][0].pattern == "https://"
+
+    @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
+    async def test_default_client_creation(self) -> None:
+        # Ensure that the client can be initialized without any exceptions
+        DefaultAsyncHttpxClient(
+            verify=True,
+            cert=None,
+            trust_env=True,
+            http1=True,
+            http2=False,
+            limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
+        )
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
+        # Test that the default follow_redirects=True allows following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+        respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
+
+        response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert response.json() == {"status": "ok"}
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
+        # Test that follow_redirects=False prevents following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+
+        with pytest.raises(APIStatusError) as exc_info:
+            await self.client.post(
+                "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
+            )
+
+        assert exc_info.value.response.status_code == 302
+        assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py
new file mode 100644
index 0000000..c498f53
--- /dev/null
+++ b/tests/test_deepcopy.py
@@ -0,0 +1,58 @@
+from gitpod._utils import deepcopy_minimal
+
+
+def assert_different_identities(obj1: object, obj2: object) -> None:
+    assert obj1 == obj2
+    assert id(obj1) != id(obj2)
+
+
+def test_simple_dict() -> None:
+    obj1 = {"foo": "bar"}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+
+
+def test_nested_dict() -> None:
+    obj1 = {"foo": {"bar": True}}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert_different_identities(obj1["foo"], obj2["foo"])
+
+
+def test_complex_nested_dict() -> None:
+    obj1 = {"foo": {"bar": [{"hello": "world"}]}}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert_different_identities(obj1["foo"], obj2["foo"])
+    assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"])
+    assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0])
+
+
+def test_simple_list() -> None:
+    obj1 = ["a", "b", "c"]
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+
+
+def test_nested_list() -> None:
+    obj1 = ["a", [1, 2, 3]]
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert_different_identities(obj1[1], obj2[1])
+
+
+class MyObject: ...
+
+
+def test_ignores_other_types() -> None:
+    # custom classes
+    my_obj = MyObject()
+    obj1 = {"foo": my_obj}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert obj1["foo"] is my_obj
+
+    # tuples
+    obj3 = ("a", "b")
+    obj4 = deepcopy_minimal(obj3)
+    assert obj3 is obj4
diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py
new file mode 100644
index 0000000..0ca5a8d
--- /dev/null
+++ b/tests/test_extract_files.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+from typing import Sequence
+
+import pytest
+
+from gitpod._types import FileTypes
+from gitpod._utils import extract_files
+
+
+def test_removes_files_from_input() -> None:
+    query = {"foo": "bar"}
+    assert extract_files(query, paths=[]) == []
+    assert query == {"foo": "bar"}
+
+    query2 = {"foo": b"Bar", "hello": "world"}
+    assert extract_files(query2, paths=[["foo"]]) == [("foo", b"Bar")]
+    assert query2 == {"hello": "world"}
+
+    query3 = {"foo": {"foo": {"bar": b"Bar"}}, "hello": "world"}
+    assert extract_files(query3, paths=[["foo", "foo", "bar"]]) == [("foo[foo][bar]", b"Bar")]
+    assert query3 == {"foo": {"foo": {}}, "hello": "world"}
+
+    query4 = {"foo": {"bar": b"Bar", "baz": "foo"}, "hello": "world"}
+    assert extract_files(query4, paths=[["foo", "bar"]]) == [("foo[bar]", b"Bar")]
+    assert query4 == {"hello": "world", "foo": {"baz": "foo"}}
+
+
+def test_multiple_files() -> None:
+    query = {"documents": [{"file": b"My first file"}, {"file": b"My second file"}]}
+    assert extract_files(query, paths=[["documents", "<array>", "file"]]) == [
+        ("documents[][file]", b"My first file"),
+        ("documents[][file]", b"My second file"),
+    ]
+    assert query == {"documents": [{}, {}]}
+
+
+@pytest.mark.parametrize(
+    "query,paths,expected",
+    [
+        [
+            {"foo": {"bar": "baz"}},
+            [["foo", "<array>", "bar"]],
+            [],
+        ],
+        [
+            {"foo": ["bar", "baz"]},
+            [["foo", "bar"]],
+            [],
+        ],
+        [
+            {"foo": {"bar": "baz"}},
+            [["foo", "foo"]],
+            [],
+        ],
+    ],
+    ids=["dict expecting array", "array expecting dict", "unknown keys"],
+)
+def test_ignores_incorrect_paths(
+    query: dict[str, object],
+    paths: Sequence[Sequence[str]],
+    expected: list[tuple[str, FileTypes]],
+) -> None:
+    assert extract_files(query, paths=paths) == expected
diff --git a/tests/test_files.py b/tests/test_files.py
new file mode 100644
index 0000000..efde0d4
--- /dev/null
+++ b/tests/test_files.py
@@ -0,0 +1,51 @@
+from pathlib import Path
+
+import anyio
+import pytest
+from dirty_equals import IsDict, IsList, IsBytes, IsTuple
+
+from gitpod._files import to_httpx_files, async_to_httpx_files
+
+readme_path = Path(__file__).parent.parent.joinpath("README.md")
+
+
+def test_pathlib_includes_file_name() -> None:
+    result = to_httpx_files({"file": readme_path})
+    print(result)
+    assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+def test_tuple_input() -> None:
+    result = to_httpx_files([("file", readme_path)])
+    print(result)
+    assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes())))
+
+
+@pytest.mark.asyncio
+async def test_async_pathlib_includes_file_name() -> None:
+    result = await async_to_httpx_files({"file": readme_path})
+    print(result)
+    assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+@pytest.mark.asyncio
+async def test_async_supports_anyio_path() -> None:
+    result = await async_to_httpx_files({"file": anyio.Path(readme_path)})
+    print(result)
+    assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+@pytest.mark.asyncio
+async def test_async_tuple_input() -> None:
+    result = await async_to_httpx_files([("file", readme_path)])
+    print(result)
+    assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes())))
+
+
+def test_string_not_allowed() -> None:
+    with pytest.raises(TypeError, match="Expected file types input to be a FileContent type or to be a tuple"):
+        to_httpx_files(
+            {
+                "file": "foo",  # type: ignore
+            }
+        )
diff --git a/tests/test_models.py b/tests/test_models.py
new file mode 100644
index 0000000..26429e1
--- /dev/null
+++ b/tests/test_models.py
@@ -0,0 +1,936 @@
+import json
+from typing import Any, Dict, List, Union, Optional, cast
+from datetime import datetime, timezone
+from typing_extensions import Literal, Annotated, TypeAliasType
+
+import pytest
+import pydantic
+from pydantic import Field
+
+from gitpod._utils import PropertyInfo
+from gitpod._compat import PYDANTIC_V2, parse_obj, model_dump, model_json
+from gitpod._models import BaseModel, construct_type
+
+
+class BasicModel(BaseModel):
+    foo: str
+
+
+@pytest.mark.parametrize("value", ["hello", 1], ids=["correct type", "mismatched"])
+def test_basic(value: object) -> None:
+    m = BasicModel.construct(foo=value)
+    assert m.foo == value
+
+
+def test_directly_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: BasicModel
+
+    m = NestedModel.construct(nested={"foo": "Foo!"})
+    assert m.nested.foo == "Foo!"
+
+    # mismatched types
+    m = NestedModel.construct(nested="hello!")
+    assert cast(Any, m.nested) == "hello!"
+
+
+def test_optional_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: Optional[BasicModel]
+
+    m1 = NestedModel.construct(nested=None)
+    assert m1.nested is None
+
+    m2 = NestedModel.construct(nested={"foo": "bar"})
+    assert m2.nested is not None
+    assert m2.nested.foo == "bar"
+
+    # mismatched types
+    m3 = NestedModel.construct(nested={"foo"})
+    assert isinstance(cast(Any, m3.nested), set)
+    assert cast(Any, m3.nested) == {"foo"}
+
+
+def test_list_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: List[BasicModel]
+
+    m = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}])
+    assert m.nested is not None
+    assert isinstance(m.nested, list)
+    assert len(m.nested) == 2
+    assert m.nested[0].foo == "bar"
+    assert m.nested[1].foo == "2"
+
+    # mismatched types
+    m = NestedModel.construct(nested=True)
+    assert cast(Any, m.nested) is True
+
+    m = NestedModel.construct(nested=[False])
+    assert cast(Any, m.nested) == [False]
+
+
+def test_optional_list_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: Optional[List[BasicModel]]
+
+    m1 = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}])
+    assert m1.nested is not None
+    assert isinstance(m1.nested, list)
+    assert len(m1.nested) == 2
+    assert m1.nested[0].foo == "bar"
+    assert m1.nested[1].foo == "2"
+
+    m2 = NestedModel.construct(nested=None)
+    assert m2.nested is None
+
+    # mismatched types
+    m3 = NestedModel.construct(nested={1})
+    assert cast(Any, m3.nested) == {1}
+
+    m4 = NestedModel.construct(nested=[False])
+    assert cast(Any, m4.nested) == [False]
+
+
+def test_list_optional_items_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: List[Optional[BasicModel]]
+
+    m = NestedModel.construct(nested=[None, {"foo": "bar"}])
+    assert m.nested is not None
+    assert isinstance(m.nested, list)
+    assert len(m.nested) == 2
+    assert m.nested[0] is None
+    assert m.nested[1] is not None
+    assert m.nested[1].foo == "bar"
+
+    # mismatched types
+    m3 = NestedModel.construct(nested="foo")
+    assert cast(Any, m3.nested) == "foo"
+
+    m4 = NestedModel.construct(nested=[False])
+    assert cast(Any, m4.nested) == [False]
+
+
+def test_list_mismatched_type() -> None:
+    class NestedModel(BaseModel):
+        nested: List[str]
+
+    m = NestedModel.construct(nested=False)
+    assert cast(Any, m.nested) is False
+
+
+def test_raw_dictionary() -> None:
+    class NestedModel(BaseModel):
+        nested: Dict[str, str]
+
+    m = NestedModel.construct(nested={"hello": "world"})
+    assert m.nested == {"hello": "world"}
+
+    # mismatched types
+    m = NestedModel.construct(nested=False)
+    assert cast(Any, m.nested) is False
+
+
+def test_nested_dictionary_model() -> None:
+    class NestedModel(BaseModel):
+        nested: Dict[str, BasicModel]
+
+    m = NestedModel.construct(nested={"hello": {"foo": "bar"}})
+    assert isinstance(m.nested, dict)
+    assert m.nested["hello"].foo == "bar"
+
+    # mismatched types
+    m = NestedModel.construct(nested={"hello": False})
+    assert cast(Any, m.nested["hello"]) is False
+
+
+def test_unknown_fields() -> None:
+    m1 = BasicModel.construct(foo="foo", unknown=1)
+    assert m1.foo == "foo"
+    assert cast(Any, m1).unknown == 1
+
+    m2 = BasicModel.construct(foo="foo", unknown={"foo_bar": True})
+    assert m2.foo == "foo"
+    assert cast(Any, m2).unknown == {"foo_bar": True}
+
+    assert model_dump(m2) == {"foo": "foo", "unknown": {"foo_bar": True}}
+
+
+def test_strict_validation_unknown_fields() -> None:
+    class Model(BaseModel):
+        foo: str
+
+    model = parse_obj(Model, dict(foo="hello!", user="Robert"))
+    assert model.foo == "hello!"
+    assert cast(Any, model).user == "Robert"
+
+    assert model_dump(model) == {"foo": "hello!", "user": "Robert"}
+
+
+def test_aliases() -> None:
+    class Model(BaseModel):
+        my_field: int = Field(alias="myField")
+
+    m = Model.construct(myField=1)
+    assert m.my_field == 1
+
+    # mismatched types
+    m = Model.construct(myField={"hello": False})
+    assert cast(Any, m.my_field) == {"hello": False}
+
+
+def test_repr() -> None:
+    model = BasicModel(foo="bar")
+    assert str(model) == "BasicModel(foo='bar')"
+    assert repr(model) == "BasicModel(foo='bar')"
+
+
+def test_repr_nested_model() -> None:
+    class Child(BaseModel):
+        name: str
+        age: int
+
+    class Parent(BaseModel):
+        name: str
+        child: Child
+
+    model = Parent(name="Robert", child=Child(name="Foo", age=5))
+    assert str(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))"
+    assert repr(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))"
+
+
+def test_optional_list() -> None:
+    class Submodel(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        items: Optional[List[Submodel]]
+
+    m = Model.construct(items=None)
+    assert m.items is None
+
+    m = Model.construct(items=[])
+    assert m.items == []
+
+    m = Model.construct(items=[{"name": "Robert"}])
+    assert m.items is not None
+    assert len(m.items) == 1
+    assert m.items[0].name == "Robert"
+
+
+def test_nested_union_of_models() -> None:
+    class Submodel1(BaseModel):
+        bar: bool
+
+    class Submodel2(BaseModel):
+        thing: str
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Submodel2]
+
+    m = Model.construct(foo={"thing": "hello"})
+    assert isinstance(m.foo, Submodel2)
+    assert m.foo.thing == "hello"
+
+
+def test_nested_union_of_mixed_types() -> None:
+    class Submodel1(BaseModel):
+        bar: bool
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Literal[True], Literal["CARD_HOLDER"]]
+
+    m = Model.construct(foo=True)
+    assert m.foo is True
+
+    m = Model.construct(foo="CARD_HOLDER")
+    assert m.foo == "CARD_HOLDER"
+
+    m = Model.construct(foo={"bar": False})
+    assert isinstance(m.foo, Submodel1)
+    assert m.foo.bar is False
+
+
+def test_nested_union_multiple_variants() -> None:
+    class Submodel1(BaseModel):
+        bar: bool
+
+    class Submodel2(BaseModel):
+        thing: str
+
+    class Submodel3(BaseModel):
+        foo: int
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Submodel2, None, Submodel3]
+
+    m = Model.construct(foo={"thing": "hello"})
+    assert isinstance(m.foo, Submodel2)
+    assert m.foo.thing == "hello"
+
+    m = Model.construct(foo=None)
+    assert m.foo is None
+
+    m = Model.construct()
+    assert m.foo is None
+
+    m = Model.construct(foo={"foo": "1"})
+    assert isinstance(m.foo, Submodel3)
+    assert m.foo.foo == 1
+
+
+def test_nested_union_invalid_data() -> None:
+    class Submodel1(BaseModel):
+        level: int
+
+    class Submodel2(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Submodel2]
+
+    m = Model.construct(foo=True)
+    assert cast(bool, m.foo) is True
+
+    m = Model.construct(foo={"name": 3})
+    if PYDANTIC_V2:
+        assert isinstance(m.foo, Submodel1)
+        assert m.foo.name == 3  # type: ignore
+    else:
+        assert isinstance(m.foo, Submodel2)
+        assert m.foo.name == "3"
+
+
+def test_list_of_unions() -> None:
+    class Submodel1(BaseModel):
+        level: int
+
+    class Submodel2(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        items: List[Union[Submodel1, Submodel2]]
+
+    m = Model.construct(items=[{"level": 1}, {"name": "Robert"}])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], Submodel1)
+    assert m.items[0].level == 1
+    assert isinstance(m.items[1], Submodel2)
+    assert m.items[1].name == "Robert"
+
+    m = Model.construct(items=[{"level": -1}, 156])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], Submodel1)
+    assert m.items[0].level == -1
+    assert cast(Any, m.items[1]) == 156
+
+
+def test_union_of_lists() -> None:
+    class SubModel1(BaseModel):
+        level: int
+
+    class SubModel2(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        items: Union[List[SubModel1], List[SubModel2]]
+
+    # with one valid entry
+    m = Model.construct(items=[{"name": "Robert"}])
+    assert len(m.items) == 1
+    assert isinstance(m.items[0], SubModel2)
+    assert m.items[0].name == "Robert"
+
+    # with two entries pointing to different types
+    m = Model.construct(items=[{"level": 1}, {"name": "Robert"}])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], SubModel1)
+    assert m.items[0].level == 1
+    assert isinstance(m.items[1], SubModel1)
+    assert cast(Any, m.items[1]).name == "Robert"
+
+    # with two entries pointing to *completely* different types
+    m = Model.construct(items=[{"level": -1}, 156])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], SubModel1)
+    assert m.items[0].level == -1
+    assert cast(Any, m.items[1]) == 156
+
+
+def test_dict_of_union() -> None:
+    class SubModel1(BaseModel):
+        name: str
+
+    class SubModel2(BaseModel):
+        foo: str
+
+    class Model(BaseModel):
+        data: Dict[str, Union[SubModel1, SubModel2]]
+
+    m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}})
+    assert len(list(m.data.keys())) == 2
+    assert isinstance(m.data["hello"], SubModel1)
+    assert m.data["hello"].name == "there"
+    assert isinstance(m.data["foo"], SubModel2)
+    assert m.data["foo"].foo == "bar"
+
+    # TODO: test mismatched type
+
+
+def test_double_nested_union() -> None:
+    class SubModel1(BaseModel):
+        name: str
+
+    class SubModel2(BaseModel):
+        bar: str
+
+    class Model(BaseModel):
+        data: Dict[str, List[Union[SubModel1, SubModel2]]]
+
+    m = Model.construct(data={"foo": [{"bar": "baz"}, {"name": "Robert"}]})
+    assert len(m.data["foo"]) == 2
+
+    entry1 = m.data["foo"][0]
+    assert isinstance(entry1, SubModel2)
+    assert entry1.bar == "baz"
+
+    entry2 = m.data["foo"][1]
+    assert isinstance(entry2, SubModel1)
+    assert entry2.name == "Robert"
+
+    # TODO: test mismatched type
+
+
+def test_union_of_dict() -> None:
+    class SubModel1(BaseModel):
+        name: str
+
+    class SubModel2(BaseModel):
+        foo: str
+
+    class Model(BaseModel):
+        data: Union[Dict[str, SubModel1], Dict[str, SubModel2]]
+
+    m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}})
+    assert len(list(m.data.keys())) == 2
+    assert isinstance(m.data["hello"], SubModel1)
+    assert m.data["hello"].name == "there"
+    assert isinstance(m.data["foo"], SubModel1)
+    assert cast(Any, m.data["foo"]).foo == "bar"
+
+
+def test_iso8601_datetime() -> None:
+    class Model(BaseModel):
+        created_at: datetime
+
+    expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc)
+
+    if PYDANTIC_V2:
+        expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}'
+    else:
+        expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}'
+
+    model = Model.construct(created_at="2019-12-27T18:11:19.117Z")
+    assert model.created_at == expected
+    assert model_json(model) == expected_json
+
+    model = parse_obj(Model, dict(created_at="2019-12-27T18:11:19.117Z"))
+    assert model.created_at == expected
+    assert model_json(model) == expected_json
+
+
+def test_does_not_coerce_int() -> None:
+    class Model(BaseModel):
+        bar: int
+
+    assert Model.construct(bar=1).bar == 1
+    assert Model.construct(bar=10.9).bar == 10.9
+    assert Model.construct(bar="19").bar == "19"  # type: ignore[comparison-overlap]
+    assert Model.construct(bar=False).bar is False
+
+
+def test_int_to_float_safe_conversion() -> None:
+    class Model(BaseModel):
+        float_field: float
+
+    m = Model.construct(float_field=10)
+    assert m.float_field == 10.0
+    assert isinstance(m.float_field, float)
+
+    m = Model.construct(float_field=10.12)
+    assert m.float_field == 10.12
+    assert isinstance(m.float_field, float)
+
+    # number too big
+    m = Model.construct(float_field=2**53 + 1)
+    assert m.float_field == 2**53 + 1
+    assert isinstance(m.float_field, int)
+
+
+def test_deprecated_alias() -> None:
+    class Model(BaseModel):
+        resource_id: str = Field(alias="model_id")
+
+        @property
+        def model_id(self) -> str:
+            return self.resource_id
+
+    m = Model.construct(model_id="id")
+    assert m.model_id == "id"
+    assert m.resource_id == "id"
+    assert m.resource_id is m.model_id
+
+    m = parse_obj(Model, {"model_id": "id"})
+    assert m.model_id == "id"
+    assert m.resource_id == "id"
+    assert m.resource_id is m.model_id
+
+
+def test_omitted_fields() -> None:
+    class Model(BaseModel):
+        resource_id: Optional[str] = None
+
+    m = Model.construct()
+    assert m.resource_id is None
+    assert "resource_id" not in m.model_fields_set
+
+    m = Model.construct(resource_id=None)
+    assert m.resource_id is None
+    assert "resource_id" in m.model_fields_set
+
+    m = Model.construct(resource_id="foo")
+    assert m.resource_id == "foo"
+    assert "resource_id" in m.model_fields_set
+
+
+def test_to_dict() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert m.to_dict() == {"FOO": "hello"}
+    assert m.to_dict(use_api_names=False) == {"foo": "hello"}
+
+    m2 = Model()
+    assert m2.to_dict() == {}
+    assert m2.to_dict(exclude_unset=False) == {"FOO": None}
+    assert m2.to_dict(exclude_unset=False, exclude_none=True) == {}
+    assert m2.to_dict(exclude_unset=False, exclude_defaults=True) == {}
+
+    m3 = Model(FOO=None)
+    assert m3.to_dict() == {"FOO": None}
+    assert m3.to_dict(exclude_none=True) == {}
+    assert m3.to_dict(exclude_defaults=True) == {}
+
+    class Model2(BaseModel):
+        created_at: datetime
+
+    time_str = "2024-03-21T11:39:01.275859"
+    m4 = Model2.construct(created_at=time_str)
+    assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)}
+    assert m4.to_dict(mode="json") == {"created_at": time_str}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.to_dict(warnings=False)
+
+
+def test_forwards_compat_model_dump_method() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert m.model_dump() == {"foo": "hello"}
+    assert m.model_dump(include={"bar"}) == {}
+    assert m.model_dump(exclude={"foo"}) == {}
+    assert m.model_dump(by_alias=True) == {"FOO": "hello"}
+
+    m2 = Model()
+    assert m2.model_dump() == {"foo": None}
+    assert m2.model_dump(exclude_unset=True) == {}
+    assert m2.model_dump(exclude_none=True) == {}
+    assert m2.model_dump(exclude_defaults=True) == {}
+
+    m3 = Model(FOO=None)
+    assert m3.model_dump() == {"foo": None}
+    assert m3.model_dump(exclude_none=True) == {}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"):
+            m.model_dump(round_trip=True)
+
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.model_dump(warnings=False)
+
+
+def test_compat_method_no_error_for_warnings() -> None:
+    class Model(BaseModel):
+        foo: Optional[str]
+
+    m = Model(foo="hello")
+    assert isinstance(model_dump(m, warnings=False), dict)
+
+
+def test_to_json() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert json.loads(m.to_json()) == {"FOO": "hello"}
+    assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"}
+
+    if PYDANTIC_V2:
+        assert m.to_json(indent=None) == '{"FOO":"hello"}'
+    else:
+        assert m.to_json(indent=None) == '{"FOO": "hello"}'
+
+    m2 = Model()
+    assert json.loads(m2.to_json()) == {}
+    assert json.loads(m2.to_json(exclude_unset=False)) == {"FOO": None}
+    assert json.loads(m2.to_json(exclude_unset=False, exclude_none=True)) == {}
+    assert json.loads(m2.to_json(exclude_unset=False, exclude_defaults=True)) == {}
+
+    m3 = Model(FOO=None)
+    assert json.loads(m3.to_json()) == {"FOO": None}
+    assert json.loads(m3.to_json(exclude_none=True)) == {}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.to_json(warnings=False)
+
+
+def test_forwards_compat_model_dump_json_method() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert json.loads(m.model_dump_json()) == {"foo": "hello"}
+    assert json.loads(m.model_dump_json(include={"bar"})) == {}
+    assert json.loads(m.model_dump_json(include={"foo"})) == {"foo": "hello"}
+    assert json.loads(m.model_dump_json(by_alias=True)) == {"FOO": "hello"}
+
+    assert m.model_dump_json(indent=2) == '{\n  "foo": "hello"\n}'
+
+    m2 = Model()
+    assert json.loads(m2.model_dump_json()) == {"foo": None}
+    assert json.loads(m2.model_dump_json(exclude_unset=True)) == {}
+    assert json.loads(m2.model_dump_json(exclude_none=True)) == {}
+    assert json.loads(m2.model_dump_json(exclude_defaults=True)) == {}
+
+    m3 = Model(FOO=None)
+    assert json.loads(m3.model_dump_json()) == {"foo": None}
+    assert json.loads(m3.model_dump_json(exclude_none=True)) == {}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"):
+            m.model_dump_json(round_trip=True)
+
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.model_dump_json(warnings=False)
+
+
+def test_type_compat() -> None:
+    # our model type can be assigned to Pydantic's model type
+
+    def takes_pydantic(model: pydantic.BaseModel) -> None:  # noqa: ARG001
+        ...
+
+    class OurModel(BaseModel):
+        foo: Optional[str] = None
+
+    takes_pydantic(OurModel())
+
+
+def test_annotated_types() -> None:
+    class Model(BaseModel):
+        value: str
+
+    m = construct_type(
+        value={"value": "foo"},
+        type_=cast(Any, Annotated[Model, "random metadata"]),
+    )
+    assert isinstance(m, Model)
+    assert m.value == "foo"
+
+
+def test_discriminated_unions_invalid_data() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    m = construct_type(
+        value={"type": "a", "data": 100},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, A)
+    assert m.type == "a"
+    if PYDANTIC_V2:
+        assert m.data == 100  # type: ignore[comparison-overlap]
+    else:
+        # pydantic v1 automatically converts inputs to strings
+        # if the expected type is a str
+        assert m.data == "100"
+
+
+def test_discriminated_unions_unknown_variant() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    m = construct_type(
+        value={"type": "c", "data": None, "new_thing": "bar"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+
+    # just chooses the first variant
+    assert isinstance(m, A)
+    assert m.type == "c"  # type: ignore[comparison-overlap]
+    assert m.data == None  # type: ignore[unreachable]
+    assert m.new_thing == "bar"
+
+
+def test_discriminated_unions_invalid_data_nested_unions() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    class C(BaseModel):
+        type: Literal["c"]
+
+        data: bool
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"},
+        type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    m = construct_type(
+        value={"type": "c", "data": "foo"},
+        type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, C)
+    assert m.type == "c"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_with_aliases_invalid_data() -> None:
+    class A(BaseModel):
+        foo_type: Literal["a"] = Field(alias="type")
+
+        data: str
+
+    class B(BaseModel):
+        foo_type: Literal["b"] = Field(alias="type")
+
+        data: int
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]),
+    )
+    assert isinstance(m, B)
+    assert m.foo_type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    m = construct_type(
+        value={"type": "a", "data": 100},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]),
+    )
+    assert isinstance(m, A)
+    assert m.foo_type == "a"
+    if PYDANTIC_V2:
+        assert m.data == 100  # type: ignore[comparison-overlap]
+    else:
+        # pydantic v1 automatically converts inputs to strings
+        # if the expected type is a str
+        assert m.data == "100"
+
+
+def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: bool
+
+    class B(BaseModel):
+        type: Literal["a"]
+
+        data: int
+
+    m = construct_type(
+        value={"type": "a", "data": "foo"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, B)
+    assert m.type == "a"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_invalid_data_uses_cache() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    UnionType = cast(Any, Union[A, B])
+
+    assert not hasattr(UnionType, "__discriminator__")
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    discriminator = UnionType.__discriminator__
+    assert discriminator is not None
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    # if the discriminator details object stays the same between invocations then
+    # we hit the cache
+    assert UnionType.__discriminator__ is discriminator
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
+def test_type_alias_type() -> None:
+    Alias = TypeAliasType("Alias", str)  # pyright: ignore
+
+    class Model(BaseModel):
+        alias: Alias
+        union: Union[int, Alias]
+
+    m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model)
+    assert isinstance(m, Model)
+    assert isinstance(m.alias, str)
+    assert m.alias == "foo"
+    assert isinstance(m.union, str)
+    assert m.union == "bar"
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
+def test_field_named_cls() -> None:
+    class Model(BaseModel):
+        cls: str
+
+    m = construct_type(value={"cls": "foo"}, type_=Model)
+    assert isinstance(m, Model)
+    assert isinstance(m.cls, str)
+
+
+def test_discriminated_union_case() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: bool
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: List[Union[A, object]]
+
+    class ModelA(BaseModel):
+        type: Literal["modelA"]
+
+        data: int
+
+    class ModelB(BaseModel):
+        type: Literal["modelB"]
+
+        required: str
+
+        data: Union[A, B]
+
+    # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required`
+    m = construct_type(
+        value={"type": "modelB", "data": {"type": "a", "data": True}},
+        type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]),
+    )
+
+    assert isinstance(m, ModelB)
+
+
+def test_nested_discriminated_union() -> None:
+    class InnerType1(BaseModel):
+        type: Literal["type_1"]
+
+    class InnerModel(BaseModel):
+        inner_value: str
+
+    class InnerType2(BaseModel):
+        type: Literal["type_2"]
+        some_inner_model: InnerModel
+
+    class Type1(BaseModel):
+        base_type: Literal["base_type_1"]
+        value: Annotated[
+            Union[
+                InnerType1,
+                InnerType2,
+            ],
+            PropertyInfo(discriminator="type"),
+        ]
+
+    class Type2(BaseModel):
+        base_type: Literal["base_type_2"]
+
+    T = Annotated[
+        Union[
+            Type1,
+            Type2,
+        ],
+        PropertyInfo(discriminator="base_type"),
+    ]
+
+    model = construct_type(
+        type_=T,
+        value={
+            "base_type": "base_type_1",
+            "value": {
+                "type": "type_2",
+            },
+        },
+    )
+    assert isinstance(model, Type1)
+    assert isinstance(model.value, InnerType2)
diff --git a/tests/test_qs.py b/tests/test_qs.py
new file mode 100644
index 0000000..0ca7188
--- /dev/null
+++ b/tests/test_qs.py
@@ -0,0 +1,78 @@
+from typing import Any, cast
+from functools import partial
+from urllib.parse import unquote
+
+import pytest
+
+from gitpod._qs import Querystring, stringify
+
+
+def test_empty() -> None:
+    assert stringify({}) == ""
+    assert stringify({"a": {}}) == ""
+    assert stringify({"a": {"b": {"c": {}}}}) == ""
+
+
+def test_basic() -> None:
+    assert stringify({"a": 1}) == "a=1"
+    assert stringify({"a": "b"}) == "a=b"
+    assert stringify({"a": True}) == "a=true"
+    assert stringify({"a": False}) == "a=false"
+    assert stringify({"a": 1.23456}) == "a=1.23456"
+    assert stringify({"a": None}) == ""
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_nested_dotted(method: str) -> None:
+    if method == "class":
+        serialise = Querystring(nested_format="dots").stringify
+    else:
+        serialise = partial(stringify, nested_format="dots")
+
+    assert unquote(serialise({"a": {"b": "c"}})) == "a.b=c"
+    assert unquote(serialise({"a": {"b": "c", "d": "e", "f": "g"}})) == "a.b=c&a.d=e&a.f=g"
+    assert unquote(serialise({"a": {"b": {"c": {"d": "e"}}}})) == "a.b.c.d=e"
+    assert unquote(serialise({"a": {"b": True}})) == "a.b=true"
+
+
+def test_nested_brackets() -> None:
+    assert unquote(stringify({"a": {"b": "c"}})) == "a[b]=c"
+    assert unquote(stringify({"a": {"b": "c", "d": "e", "f": "g"}})) == "a[b]=c&a[d]=e&a[f]=g"
+    assert unquote(stringify({"a": {"b": {"c": {"d": "e"}}}})) == "a[b][c][d]=e"
+    assert unquote(stringify({"a": {"b": True}})) == "a[b]=true"
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_array_comma(method: str) -> None:
+    if method == "class":
+        serialise = Querystring(array_format="comma").stringify
+    else:
+        serialise = partial(stringify, array_format="comma")
+
+    assert unquote(serialise({"in": ["foo", "bar"]})) == "in=foo,bar"
+    assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b]=true,false"
+    assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b]=true,false,true"
+
+
+def test_array_repeat() -> None:
+    assert unquote(stringify({"in": ["foo", "bar"]})) == "in=foo&in=bar"
+    assert unquote(stringify({"a": {"b": [True, False]}})) == "a[b]=true&a[b]=false"
+    assert unquote(stringify({"a": {"b": [True, False, None, True]}})) == "a[b]=true&a[b]=false&a[b]=true"
+    assert unquote(stringify({"in": ["foo", {"b": {"c": ["d", "e"]}}]})) == "in=foo&in[b][c]=d&in[b][c]=e"
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_array_brackets(method: str) -> None:
+    if method == "class":
+        serialise = Querystring(array_format="brackets").stringify
+    else:
+        serialise = partial(stringify, array_format="brackets")
+
+    assert unquote(serialise({"in": ["foo", "bar"]})) == "in[]=foo&in[]=bar"
+    assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b][]=true&a[b][]=false"
+    assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b][]=true&a[b][]=false&a[b][]=true"
+
+
+def test_unknown_array_format() -> None:
+    with pytest.raises(NotImplementedError, match="Unknown array_format value: foo, choose from comma, repeat"):
+        stringify({"a": ["foo", "bar"]}, array_format=cast(Any, "foo"))
diff --git a/tests/test_required_args.py b/tests/test_required_args.py
new file mode 100644
index 0000000..84527ed
--- /dev/null
+++ b/tests/test_required_args.py
@@ -0,0 +1,111 @@
+from __future__ import annotations
+
+import pytest
+
+from gitpod._utils import required_args
+
+
+def test_too_many_positional_params() -> None:
+    @required_args(["a"])
+    def foo(a: str | None = None) -> str | None:
+        return a
+
+    with pytest.raises(TypeError, match=r"foo\(\) takes 1 argument\(s\) but 2 were given"):
+        foo("a", "b")  # type: ignore
+
+
+def test_positional_param() -> None:
+    @required_args(["a"])
+    def foo(a: str | None = None) -> str | None:
+        return a
+
+    assert foo("a") == "a"
+    assert foo(None) is None
+    assert foo(a="b") == "b"
+
+    with pytest.raises(TypeError, match="Missing required argument: 'a'"):
+        foo()
+
+
+def test_keyword_only_param() -> None:
+    @required_args(["a"])
+    def foo(*, a: str | None = None) -> str | None:
+        return a
+
+    assert foo(a="a") == "a"
+    assert foo(a=None) is None
+    assert foo(a="b") == "b"
+
+    with pytest.raises(TypeError, match="Missing required argument: 'a'"):
+        foo()
+
+
+def test_multiple_params() -> None:
+    @required_args(["a", "b", "c"])
+    def foo(a: str = "", *, b: str = "", c: str = "") -> str | None:
+        return f"{a} {b} {c}"
+
+    assert foo(a="a", b="b", c="c") == "a b c"
+
+    error_message = r"Missing required arguments.*"
+
+    with pytest.raises(TypeError, match=error_message):
+        foo()
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(a="a")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(b="b")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(c="c")
+
+    with pytest.raises(TypeError, match=r"Missing required argument: 'a'"):
+        foo(b="a", c="c")
+
+    with pytest.raises(TypeError, match=r"Missing required argument: 'b'"):
+        foo("a", c="c")
+
+
+def test_multiple_variants() -> None:
+    @required_args(["a"], ["b"])
+    def foo(*, a: str | None = None, b: str | None = None) -> str | None:
+        return a if a is not None else b
+
+    assert foo(a="foo") == "foo"
+    assert foo(b="bar") == "bar"
+    assert foo(a=None) is None
+    assert foo(b=None) is None
+
+    # TODO: this error message could probably be improved
+    with pytest.raises(
+        TypeError,
+        match=r"Missing required arguments; Expected either \('a'\) or \('b'\) arguments to be given",
+    ):
+        foo()
+
+
+def test_multiple_params_multiple_variants() -> None:
+    @required_args(["a", "b"], ["c"])
+    def foo(*, a: str | None = None, b: str | None = None, c: str | None = None) -> str | None:
+        if a is not None:
+            return a
+        if b is not None:
+            return b
+        return c
+
+    error_message = r"Missing required arguments; Expected either \('a' and 'b'\) or \('c'\) arguments to be given"
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(a="foo")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(b="bar")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo()
+
+    assert foo(a=None, b="bar") == "bar"
+    assert foo(c=None) is None
+    assert foo(c="foo") == "foo"
diff --git a/tests/test_response.py b/tests/test_response.py
new file mode 100644
index 0000000..8e6ccd7
--- /dev/null
+++ b/tests/test_response.py
@@ -0,0 +1,277 @@
+import json
+from typing import Any, List, Union, cast
+from typing_extensions import Annotated
+
+import httpx
+import pytest
+import pydantic
+
+from gitpod import Gitpod, BaseModel, AsyncGitpod
+from gitpod._response import (
+    APIResponse,
+    BaseAPIResponse,
+    AsyncAPIResponse,
+    BinaryAPIResponse,
+    AsyncBinaryAPIResponse,
+    extract_response_type,
+)
+from gitpod._streaming import Stream
+from gitpod._base_client import FinalRequestOptions
+
+
+class ConcreteBaseAPIResponse(APIResponse[bytes]): ...
+
+
+class ConcreteAPIResponse(APIResponse[List[str]]): ...
+
+
+class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ...
+
+
+def test_extract_response_type_direct_classes() -> None:
+    assert extract_response_type(BaseAPIResponse[str]) == str
+    assert extract_response_type(APIResponse[str]) == str
+    assert extract_response_type(AsyncAPIResponse[str]) == str
+
+
+def test_extract_response_type_direct_class_missing_type_arg() -> None:
+    with pytest.raises(
+        RuntimeError,
+        match="Expected type <class 'gitpod._response.AsyncAPIResponse'> to have a type argument at index 0 but it did not",
+    ):
+        extract_response_type(AsyncAPIResponse)
+
+
+def test_extract_response_type_concrete_subclasses() -> None:
+    assert extract_response_type(ConcreteBaseAPIResponse) == bytes
+    assert extract_response_type(ConcreteAPIResponse) == List[str]
+    assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response
+
+
+def test_extract_response_type_binary_response() -> None:
+    assert extract_response_type(BinaryAPIResponse) == bytes
+    assert extract_response_type(AsyncBinaryAPIResponse) == bytes
+
+
+class PydanticModel(pydantic.BaseModel): ...
+
+
+def test_response_parse_mismatched_basemodel(client: Gitpod) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    with pytest.raises(
+        TypeError,
+        match="Pydantic models must subclass our base model type, e.g. `from gitpod import BaseModel`",
+    ):
+        response.parse(to=PydanticModel)
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_mismatched_basemodel(async_client: AsyncGitpod) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    with pytest.raises(
+        TypeError,
+        match="Pydantic models must subclass our base model type, e.g. `from gitpod import BaseModel`",
+    ):
+        await response.parse(to=PydanticModel)
+
+
+def test_response_parse_custom_stream(client: Gitpod) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=client,
+        stream=True,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    stream = response.parse(to=Stream[int])
+    assert stream._cast_to == int
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_custom_stream(async_client: AsyncGitpod) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=async_client,
+        stream=True,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    stream = await response.parse(to=Stream[int])
+    assert stream._cast_to == int
+
+
+class CustomModel(BaseModel):
+    foo: str
+    bar: int
+
+
+def test_response_parse_custom_model(client: Gitpod) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = response.parse(to=CustomModel)
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_custom_model(async_client: AsyncGitpod) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = await response.parse(to=CustomModel)
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+def test_response_parse_annotated_type(client: Gitpod) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = response.parse(
+        to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]),
+    )
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+async def test_async_response_parse_annotated_type(async_client: AsyncGitpod) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = await response.parse(
+        to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]),
+    )
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+@pytest.mark.parametrize(
+    "content, expected",
+    [
+        ("false", False),
+        ("true", True),
+        ("False", False),
+        ("True", True),
+        ("TrUe", True),
+        ("FalSe", False),
+    ],
+)
+def test_response_parse_bool(client: Gitpod, content: str, expected: bool) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=content),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    result = response.parse(to=bool)
+    assert result is expected
+
+
+@pytest.mark.parametrize(
+    "content, expected",
+    [
+        ("false", False),
+        ("true", True),
+        ("False", False),
+        ("True", True),
+        ("TrUe", True),
+        ("FalSe", False),
+    ],
+)
+async def test_async_response_parse_bool(client: AsyncGitpod, content: str, expected: bool) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=content),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    result = await response.parse(to=bool)
+    assert result is expected
+
+
+class OtherModel(BaseModel):
+    a: str
+
+
+@pytest.mark.parametrize("client", [False], indirect=True)  # loose validation
+def test_response_parse_expect_model_union_non_json_content(client: Gitpod) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
+    assert isinstance(obj, str)
+    assert obj == "foo"
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("async_client", [False], indirect=True)  # loose validation
+async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncGitpod) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = await response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
+    assert isinstance(obj, str)
+    assert obj == "foo"
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
new file mode 100644
index 0000000..7dedcf3
--- /dev/null
+++ b/tests/test_streaming.py
@@ -0,0 +1,248 @@
+from __future__ import annotations
+
+from typing import Iterator, AsyncIterator
+
+import httpx
+import pytest
+
+from gitpod import Gitpod, AsyncGitpod
+from gitpod._streaming import Stream, AsyncStream, ServerSentEvent
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_basic(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: completion\n"
+        yield b'data: {"foo":true}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "completion"
+    assert sse.json() == {"foo": True}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_data_missing_event(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'data: {"foo":true}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"foo": True}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_event_missing_data(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.data == ""
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_events(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"\n"
+        yield b"event: completion\n"
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.data == ""
+
+    sse = await iter_next(iterator)
+    assert sse.event == "completion"
+    assert sse.data == ""
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_events_with_data(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b'data: {"foo":true}\n'
+        yield b"\n"
+        yield b"event: completion\n"
+        yield b'data: {"bar":false}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": True}
+
+    sse = await iter_next(iterator)
+    assert sse.event == "completion"
+    assert sse.json() == {"bar": False}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_data_lines_with_empty_line(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"data: {\n"
+        yield b'data: "foo":\n'
+        yield b"data: \n"
+        yield b"data:\n"
+        yield b"data: true}\n"
+        yield b"\n\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": True}
+    assert sse.data == '{\n"foo":\n\n\ntrue}'
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_data_json_escaped_double_new_line(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b'data: {"foo": "my long\\n\\ncontent"}'
+        yield b"\n\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": "my long\n\ncontent"}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_data_lines(sync: bool, client: Gitpod, async_client: AsyncGitpod) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"data: {\n"
+        yield b'data: "foo":\n'
+        yield b"data: true}\n"
+        yield b"\n\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": True}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_special_new_line_character(
+    sync: bool,
+    client: Gitpod,
+    async_client: AsyncGitpod,
+) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'data: {"content":" culpa"}\n'
+        yield b"\n"
+        yield b'data: {"content":" \xe2\x80\xa8"}\n'
+        yield b"\n"
+        yield b'data: {"content":"foo"}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": " culpa"}
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": " 
"}
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": "foo"}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multi_byte_character_multiple_chunks(
+    sync: bool,
+    client: Gitpod,
+    async_client: AsyncGitpod,
+) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'data: {"content":"'
+        # bytes taken from the string 'известни' and arbitrarily split
+        # so that some multi-byte characters span multiple chunks
+        yield b"\xd0"
+        yield b"\xb8\xd0\xb7\xd0"
+        yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8"
+        yield b'"}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": "известни"}
+
+
+async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]:
+    for chunk in iter:
+        yield chunk
+
+
+async def iter_next(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> ServerSentEvent:
+    if isinstance(iter, AsyncIterator):
+        return await iter.__anext__()
+
+    return next(iter)
+
+
+async def assert_empty_iter(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> None:
+    with pytest.raises((StopAsyncIteration, RuntimeError)):
+        await iter_next(iter)
+
+
+def make_event_iterator(
+    content: Iterator[bytes],
+    *,
+    sync: bool,
+    client: Gitpod,
+    async_client: AsyncGitpod,
+) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]:
+    if sync:
+        return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events()
+
+    return AsyncStream(
+        cast_to=object, client=async_client, response=httpx.Response(200, content=to_aiter(content))
+    )._iter_events()
diff --git a/tests/test_transform.py b/tests/test_transform.py
new file mode 100644
index 0000000..2a74ad4
--- /dev/null
+++ b/tests/test_transform.py
@@ -0,0 +1,453 @@
+from __future__ import annotations
+
+import io
+import pathlib
+from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast
+from datetime import date, datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+import pytest
+
+from gitpod._types import NOT_GIVEN, Base64FileInput
+from gitpod._utils import (
+    PropertyInfo,
+    transform as _transform,
+    parse_datetime,
+    async_transform as _async_transform,
+)
+from gitpod._compat import PYDANTIC_V2
+from gitpod._models import BaseModel
+
+_T = TypeVar("_T")
+
+SAMPLE_FILE_PATH = pathlib.Path(__file__).parent.joinpath("sample_file.txt")
+
+
+async def transform(
+    data: _T,
+    expected_type: object,
+    use_async: bool,
+) -> _T:
+    if use_async:
+        return await _async_transform(data, expected_type=expected_type)
+
+    return _transform(data, expected_type=expected_type)
+
+
+parametrize = pytest.mark.parametrize("use_async", [False, True], ids=["sync", "async"])
+
+
+class Foo1(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_top_level_alias(use_async: bool) -> None:
+    assert await transform({"foo_bar": "hello"}, expected_type=Foo1, use_async=use_async) == {"fooBar": "hello"}
+
+
+class Foo2(TypedDict):
+    bar: Bar2
+
+
+class Bar2(TypedDict):
+    this_thing: Annotated[int, PropertyInfo(alias="this__thing")]
+    baz: Annotated[Baz2, PropertyInfo(alias="Baz")]
+
+
+class Baz2(TypedDict):
+    my_baz: Annotated[str, PropertyInfo(alias="myBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_recursive_typeddict(use_async: bool) -> None:
+    assert await transform({"bar": {"this_thing": 1}}, Foo2, use_async) == {"bar": {"this__thing": 1}}
+    assert await transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2, use_async) == {"bar": {"Baz": {"myBaz": "foo"}}}
+
+
+class Foo3(TypedDict):
+    things: List[Bar3]
+
+
+class Bar3(TypedDict):
+    my_field: Annotated[str, PropertyInfo(alias="myField")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_list_of_typeddict(use_async: bool) -> None:
+    result = await transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, Foo3, use_async)
+    assert result == {"things": [{"myField": "foo"}, {"myField": "foo2"}]}
+
+
+class Foo4(TypedDict):
+    foo: Union[Bar4, Baz4]
+
+
+class Bar4(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz4(TypedDict):
+    foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_of_typeddict(use_async: bool) -> None:
+    assert await transform({"foo": {"foo_bar": "bar"}}, Foo4, use_async) == {"foo": {"fooBar": "bar"}}
+    assert await transform({"foo": {"foo_baz": "baz"}}, Foo4, use_async) == {"foo": {"fooBaz": "baz"}}
+    assert await transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4, use_async) == {
+        "foo": {"fooBaz": "baz", "fooBar": "bar"}
+    }
+
+
+class Foo5(TypedDict):
+    foo: Annotated[Union[Bar4, List[Baz4]], PropertyInfo(alias="FOO")]
+
+
+class Bar5(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz5(TypedDict):
+    foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_of_list(use_async: bool) -> None:
+    assert await transform({"foo": {"foo_bar": "bar"}}, Foo5, use_async) == {"FOO": {"fooBar": "bar"}}
+    assert await transform(
+        {
+            "foo": [
+                {"foo_baz": "baz"},
+                {"foo_baz": "baz"},
+            ]
+        },
+        Foo5,
+        use_async,
+    ) == {"FOO": [{"fooBaz": "baz"}, {"fooBaz": "baz"}]}
+
+
+class Foo6(TypedDict):
+    bar: Annotated[str, PropertyInfo(alias="Bar")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_includes_unknown_keys(use_async: bool) -> None:
+    assert await transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6, use_async) == {
+        "Bar": "bar",
+        "baz_": {"FOO": 1},
+    }
+
+
+class Foo7(TypedDict):
+    bar: Annotated[List[Bar7], PropertyInfo(alias="bAr")]
+    foo: Bar7
+
+
+class Bar7(TypedDict):
+    foo: str
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_ignores_invalid_input(use_async: bool) -> None:
+    assert await transform({"bar": "<foo>"}, Foo7, use_async) == {"bAr": "<foo>"}
+    assert await transform({"foo": "<foo>"}, Foo7, use_async) == {"foo": "<foo>"}
+
+
+class DatetimeDict(TypedDict, total=False):
+    foo: Annotated[datetime, PropertyInfo(format="iso8601")]
+
+    bar: Annotated[Optional[datetime], PropertyInfo(format="iso8601")]
+
+    required: Required[Annotated[Optional[datetime], PropertyInfo(format="iso8601")]]
+
+    list_: Required[Annotated[Optional[List[datetime]], PropertyInfo(format="iso8601")]]
+
+    union: Annotated[Union[int, datetime], PropertyInfo(format="iso8601")]
+
+
+class DateDict(TypedDict, total=False):
+    foo: Annotated[date, PropertyInfo(format="iso8601")]
+
+
+class DatetimeModel(BaseModel):
+    foo: datetime
+
+
+class DateModel(BaseModel):
+    foo: Optional[date]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iso8601_format(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    tz = "Z" if PYDANTIC_V2 else "+00:00"
+    assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"}  # type: ignore[comparison-overlap]
+    assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz}  # type: ignore[comparison-overlap]
+
+    dt = dt.replace(tzinfo=None)
+    assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"}  # type: ignore[comparison-overlap]
+    assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"}  # type: ignore[comparison-overlap]
+
+    assert await transform({"foo": None}, DateDict, use_async) == {"foo": None}  # type: ignore[comparison-overlap]
+    assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None}  # type: ignore
+    assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"}  # type: ignore[comparison-overlap]
+    assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == {
+        "foo": "2023-02-23"
+    }  # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_optional_iso8601_format(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    assert await transform({"bar": dt}, DatetimeDict, use_async) == {"bar": "2023-02-23T14:16:36.337692+00:00"}  # type: ignore[comparison-overlap]
+
+    assert await transform({"bar": None}, DatetimeDict, use_async) == {"bar": None}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_required_iso8601_format(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    assert await transform({"required": dt}, DatetimeDict, use_async) == {
+        "required": "2023-02-23T14:16:36.337692+00:00"
+    }  # type: ignore[comparison-overlap]
+
+    assert await transform({"required": None}, DatetimeDict, use_async) == {"required": None}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_datetime(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    assert await transform({"union": dt}, DatetimeDict, use_async) == {  # type: ignore[comparison-overlap]
+        "union": "2023-02-23T14:16:36.337692+00:00"
+    }
+
+    assert await transform({"union": "foo"}, DatetimeDict, use_async) == {"union": "foo"}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_nested_list_iso6801_format(use_async: bool) -> None:
+    dt1 = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    dt2 = parse_datetime("2022-01-15T06:34:23Z")
+    assert await transform({"list_": [dt1, dt2]}, DatetimeDict, use_async) == {  # type: ignore[comparison-overlap]
+        "list_": ["2023-02-23T14:16:36.337692+00:00", "2022-01-15T06:34:23+00:00"]
+    }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_datetime_custom_format(use_async: bool) -> None:
+    dt = parse_datetime("2022-01-15T06:34:23Z")
+
+    result = await transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")], use_async)
+    assert result == "06"  # type: ignore[comparison-overlap]
+
+
+class DateDictWithRequiredAlias(TypedDict, total=False):
+    required_prop: Required[Annotated[date, PropertyInfo(format="iso8601", alias="prop")]]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_datetime_with_alias(use_async: bool) -> None:
+    assert await transform({"required_prop": None}, DateDictWithRequiredAlias, use_async) == {"prop": None}  # type: ignore[comparison-overlap]
+    assert await transform(
+        {"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias, use_async
+    ) == {"prop": "2023-02-23"}  # type: ignore[comparison-overlap]
+
+
+class MyModel(BaseModel):
+    foo: str
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_model_to_dictionary(use_async: bool) -> None:
+    assert cast(Any, await transform(MyModel(foo="hi!"), Any, use_async)) == {"foo": "hi!"}
+    assert cast(Any, await transform(MyModel.construct(foo="hi!"), Any, use_async)) == {"foo": "hi!"}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_empty_model(use_async: bool) -> None:
+    assert cast(Any, await transform(MyModel.construct(), Any, use_async)) == {}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_unknown_field(use_async: bool) -> None:
+    assert cast(Any, await transform(MyModel.construct(my_untyped_field=True), Any, use_async)) == {
+        "my_untyped_field": True
+    }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_mismatched_types(use_async: bool) -> None:
+    model = MyModel.construct(foo=True)
+    if PYDANTIC_V2:
+        with pytest.warns(UserWarning):
+            params = await transform(model, Any, use_async)
+    else:
+        params = await transform(model, Any, use_async)
+    assert cast(Any, params) == {"foo": True}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_mismatched_object_type(use_async: bool) -> None:
+    model = MyModel.construct(foo=MyModel.construct(hello="world"))
+    if PYDANTIC_V2:
+        with pytest.warns(UserWarning):
+            params = await transform(model, Any, use_async)
+    else:
+        params = await transform(model, Any, use_async)
+    assert cast(Any, params) == {"foo": {"hello": "world"}}
+
+
+class ModelNestedObjects(BaseModel):
+    nested: MyModel
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_nested_objects(use_async: bool) -> None:
+    model = ModelNestedObjects.construct(nested={"foo": "stainless"})
+    assert isinstance(model.nested, MyModel)
+    assert cast(Any, await transform(model, Any, use_async)) == {"nested": {"foo": "stainless"}}
+
+
+class ModelWithDefaultField(BaseModel):
+    foo: str
+    with_none_default: Union[str, None] = None
+    with_str_default: str = "foo"
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_default_field(use_async: bool) -> None:
+    # should be excluded when defaults are used
+    model = ModelWithDefaultField.construct()
+    assert model.with_none_default is None
+    assert model.with_str_default == "foo"
+    assert cast(Any, await transform(model, Any, use_async)) == {}
+
+    # should be included when the default value is explicitly given
+    model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo")
+    assert model.with_none_default is None
+    assert model.with_str_default == "foo"
+    assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": None, "with_str_default": "foo"}
+
+    # should be included when a non-default value is explicitly given
+    model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz")
+    assert model.with_none_default == "bar"
+    assert model.with_str_default == "baz"
+    assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"}
+
+
+class TypedDictIterableUnion(TypedDict):
+    foo: Annotated[Union[Bar8, Iterable[Baz8]], PropertyInfo(alias="FOO")]
+
+
+class Bar8(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz8(TypedDict):
+    foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iterable_of_dictionaries(use_async: bool) -> None:
+    assert await transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion, use_async) == {
+        "FOO": [{"fooBaz": "bar"}]
+    }
+    assert cast(Any, await transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion, use_async)) == {
+        "FOO": [{"fooBaz": "bar"}]
+    }
+
+    def my_iter() -> Iterable[Baz8]:
+        yield {"foo_baz": "hello"}
+        yield {"foo_baz": "world"}
+
+    assert await transform({"foo": my_iter()}, TypedDictIterableUnion, use_async) == {
+        "FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}]
+    }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_dictionary_items(use_async: bool) -> None:
+    class DictItems(TypedDict):
+        foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+    assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}}
+
+
+class TypedDictIterableUnionStr(TypedDict):
+    foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iterable_union_str(use_async: bool) -> None:
+    assert await transform({"foo": "bar"}, TypedDictIterableUnionStr, use_async) == {"FOO": "bar"}
+    assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [
+        {"fooBaz": "bar"}
+    ]
+
+
+class TypedDictBase64Input(TypedDict):
+    foo: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_base64_file_input(use_async: bool) -> None:
+    # strings are left as-is
+    assert await transform({"foo": "bar"}, TypedDictBase64Input, use_async) == {"foo": "bar"}
+
+    # pathlib.Path is automatically converted to base64
+    assert await transform({"foo": SAMPLE_FILE_PATH}, TypedDictBase64Input, use_async) == {
+        "foo": "SGVsbG8sIHdvcmxkIQo="
+    }  # type: ignore[comparison-overlap]
+
+    # io instances are automatically converted to base64
+    assert await transform({"foo": io.StringIO("Hello, world!")}, TypedDictBase64Input, use_async) == {
+        "foo": "SGVsbG8sIHdvcmxkIQ=="
+    }  # type: ignore[comparison-overlap]
+    assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
+        "foo": "SGVsbG8sIHdvcmxkIQ=="
+    }  # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_transform_skipping(use_async: bool) -> None:
+    # lists of ints are left as-is
+    data = [1, 2, 3]
+    assert await transform(data, List[int], use_async) is data
+
+    # iterables of ints are converted to a list
+    data = iter([1, 2, 3])
+    assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_strips_notgiven(use_async: bool) -> None:
+    assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
+    assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}
diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py
new file mode 100644
index 0000000..ec41af2
--- /dev/null
+++ b/tests/test_utils/test_proxy.py
@@ -0,0 +1,34 @@
+import operator
+from typing import Any
+from typing_extensions import override
+
+from gitpod._utils import LazyProxy
+
+
+class RecursiveLazyProxy(LazyProxy[Any]):
+    @override
+    def __load__(self) -> Any:
+        return self
+
+    def __call__(self, *_args: Any, **_kwds: Any) -> Any:
+        raise RuntimeError("This should never be called!")
+
+
+def test_recursive_proxy() -> None:
+    proxy = RecursiveLazyProxy()
+    assert repr(proxy) == "RecursiveLazyProxy"
+    assert str(proxy) == "RecursiveLazyProxy"
+    assert dir(proxy) == []
+    assert type(proxy).__name__ == "RecursiveLazyProxy"
+    assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy"
+
+
+def test_isinstance_does_not_error() -> None:
+    class AlwaysErrorProxy(LazyProxy[Any]):
+        @override
+        def __load__(self) -> Any:
+            raise RuntimeError("Mocking missing dependency")
+
+    proxy = AlwaysErrorProxy()
+    assert not isinstance(proxy, dict)
+    assert isinstance(proxy, LazyProxy)
diff --git a/tests/test_utils/test_typing.py b/tests/test_utils/test_typing.py
new file mode 100644
index 0000000..18d4ff6
--- /dev/null
+++ b/tests/test_utils/test_typing.py
@@ -0,0 +1,73 @@
+from __future__ import annotations
+
+from typing import Generic, TypeVar, cast
+
+from gitpod._utils import extract_type_var_from_base
+
+_T = TypeVar("_T")
+_T2 = TypeVar("_T2")
+_T3 = TypeVar("_T3")
+
+
+class BaseGeneric(Generic[_T]): ...
+
+
+class SubclassGeneric(BaseGeneric[_T]): ...
+
+
+class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): ...
+
+
+class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): ...
+
+
+class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): ...
+
+
+def test_extract_type_var() -> None:
+    assert (
+        extract_type_var_from_base(
+            BaseGeneric[int],
+            index=0,
+            generic_bases=cast("tuple[type, ...]", (BaseGeneric,)),
+        )
+        == int
+    )
+
+
+def test_extract_type_var_generic_subclass() -> None:
+    assert (
+        extract_type_var_from_base(
+            SubclassGeneric[int],
+            index=0,
+            generic_bases=cast("tuple[type, ...]", (BaseGeneric,)),
+        )
+        == int
+    )
+
+
+def test_extract_type_var_multiple() -> None:
+    typ = BaseGenericMultipleTypeArgs[int, str, None]
+
+    generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+    assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+    assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+    assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
+
+
+def test_extract_type_var_generic_subclass_multiple() -> None:
+    typ = SubclassGenericMultipleTypeArgs[int, str, None]
+
+    generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+    assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+    assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+    assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
+
+
+def test_extract_type_var_generic_subclass_different_ordering_multiple() -> None:
+    typ = SubclassDifferentOrderGenericMultipleTypeArgs[int, str, None]
+
+    generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+    assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+    assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+    assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644
index 0000000..d7dbee6
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,159 @@
+from __future__ import annotations
+
+import os
+import inspect
+import traceback
+import contextlib
+from typing import Any, TypeVar, Iterator, cast
+from datetime import date, datetime
+from typing_extensions import Literal, get_args, get_origin, assert_type
+
+from gitpod._types import Omit, NoneType
+from gitpod._utils import (
+    is_dict,
+    is_list,
+    is_list_type,
+    is_union_type,
+    extract_type_arg,
+    is_annotated_type,
+    is_type_alias_type,
+)
+from gitpod._compat import PYDANTIC_V2, field_outer_type, get_model_fields
+from gitpod._models import BaseModel
+
+BaseModelT = TypeVar("BaseModelT", bound=BaseModel)
+
+
+def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool:
+    for name, field in get_model_fields(model).items():
+        field_value = getattr(value, name)
+        if PYDANTIC_V2:
+            allow_none = False
+        else:
+            # in v1 nullability was structured differently
+            # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields
+            allow_none = getattr(field, "allow_none", False)
+
+        assert_matches_type(
+            field_outer_type(field),
+            field_value,
+            path=[*path, name],
+            allow_none=allow_none,
+        )
+
+    return True
+
+
+# Note: the `path` argument is only used to improve error messages when `--showlocals` is used
+def assert_matches_type(
+    type_: Any,
+    value: object,
+    *,
+    path: list[str],
+    allow_none: bool = False,
+) -> None:
+    if is_type_alias_type(type_):
+        type_ = type_.__value__
+
+    # unwrap `Annotated[T, ...]` -> `T`
+    if is_annotated_type(type_):
+        type_ = extract_type_arg(type_, 0)
+
+    if allow_none and value is None:
+        return
+
+    if type_ is None or type_ is NoneType:
+        assert value is None
+        return
+
+    origin = get_origin(type_) or type_
+
+    if is_list_type(type_):
+        return _assert_list_type(type_, value)
+
+    if origin == str:
+        assert isinstance(value, str)
+    elif origin == int:
+        assert isinstance(value, int)
+    elif origin == bool:
+        assert isinstance(value, bool)
+    elif origin == float:
+        assert isinstance(value, float)
+    elif origin == bytes:
+        assert isinstance(value, bytes)
+    elif origin == datetime:
+        assert isinstance(value, datetime)
+    elif origin == date:
+        assert isinstance(value, date)
+    elif origin == object:
+        # nothing to do here, the expected type is unknown
+        pass
+    elif origin == Literal:
+        assert value in get_args(type_)
+    elif origin == dict:
+        assert is_dict(value)
+
+        args = get_args(type_)
+        key_type = args[0]
+        items_type = args[1]
+
+        for key, item in value.items():
+            assert_matches_type(key_type, key, path=[*path, "<dict key>"])
+            assert_matches_type(items_type, item, path=[*path, "<dict item>"])
+    elif is_union_type(type_):
+        variants = get_args(type_)
+
+        try:
+            none_index = variants.index(type(None))
+        except ValueError:
+            pass
+        else:
+            # special case Optional[T] for better error messages
+            if len(variants) == 2:
+                if value is None:
+                    # valid
+                    return
+
+                return assert_matches_type(type_=variants[not none_index], value=value, path=path)
+
+        for i, variant in enumerate(variants):
+            try:
+                assert_matches_type(variant, value, path=[*path, f"variant {i}"])
+                return
+            except AssertionError:
+                traceback.print_exc()
+                continue
+
+        raise AssertionError("Did not match any variants")
+    elif issubclass(origin, BaseModel):
+        assert isinstance(value, type_)
+        assert assert_matches_model(type_, cast(Any, value), path=path)
+    elif inspect.isclass(origin) and origin.__name__ == "HttpxBinaryResponseContent":
+        assert value.__class__.__name__ == "HttpxBinaryResponseContent"
+    else:
+        assert None, f"Unhandled field type: {type_}"
+
+
+def _assert_list_type(type_: type[object], value: object) -> None:
+    assert is_list(value)
+
+    inner_type = get_args(type_)[0]
+    for entry in value:
+        assert_type(inner_type, entry)  # type: ignore
+
+
+@contextlib.contextmanager
+def update_env(**new_env: str | Omit) -> Iterator[None]:
+    old = os.environ.copy()
+
+    try:
+        for name, value in new_env.items():
+            if isinstance(value, Omit):
+                os.environ.pop(name, None)
+            else:
+                os.environ[name] = value
+
+        yield None
+    finally:
+        os.environ.clear()
+        os.environ.update(old)