diff --git a/.flake8 b/.flake8
index 0c60e1e3..2ddf68b7 100644
--- a/.flake8
+++ b/.flake8
@@ -1,6 +1,5 @@
 [flake8]
 max-line-length = 120
 exclude =
-    ./sdk/python/sdk/**,
     ./build/**
 
diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml
index 84fa323c..381e5d3b 100644
--- a/.github/workflows/build-wheels.yml
+++ b/.github/workflows/build-wheels.yml
@@ -27,7 +27,7 @@ jobs:
     needs: enforce_stable_semver
     defaults:
       run:
-        working-directory: sdk/python/sdk/zrok
+        working-directory: sdk/python/src
     strategy:
       fail-fast: false
       matrix:
@@ -62,7 +62,7 @@ jobs:
         if: startsWith(matrix.spec.name, 'linux')
         with:
           name: zrok_sdk_${{ matrix.spec.target }}
-          path: ${{ github.workspace }}/sdk/python/sdk/zrok/dist/*
+          path: dist/*
 
   publish-testpypi:
     runs-on: ubuntu-20.04
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 098d79a9..d947327a 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -72,7 +72,7 @@ jobs:
 
       - name: python lint
         shell: bash
-        run: flake8 sdk/python/sdk/zrok
+        run: flake8 sdk/python/src
 
       - name: solve GOBIN
         id: solve_go_bin
@@ -91,6 +91,9 @@ jobs:
   pytest:
 
     runs-on: ubuntu-24.04
+    defaults:
+      run:
+        working-directory: sdk/python/src
     strategy:
       matrix:
         python-version: ["3.10", "3.11", "3.12", "3.13"]
@@ -105,7 +108,6 @@ jobs:
 
       - name: Install dependencies
         shell: bash
-        working-directory: sdk/python/src/
         run: |
 
           set -o pipefail
@@ -118,7 +120,6 @@ jobs:
 
       - name: Test with pytest
         shell: bash
-        working-directory: sdk/python/src/
         run: |
 
           set -o pipefail
diff --git a/.gitignore b/.gitignore
index 8f53b79d..46267f3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,8 +16,6 @@ node_modules/
 .docusaurus
 .cache-loader
 
-sdk/python/sdk/build/
-
 # Misc
 .DS_Store
 .env.local
@@ -39,6 +37,7 @@ yarn-error.log*
 sdk/nodejs/sdk/dist
 
 # py module artifacts
-*.egg-info/
-__pycache__/
-.coverage
+/sdk/python/src/**/*.egg-info/
+/sdk/python/src/**/__pycache__/
+/.coverage
+/sdk/python/src/dist/
diff --git a/sdk/python/src/zrok/environment/root.py b/sdk/python/src/zrok/environment/root.py
index 8fb97af6..6db7d2f6 100644
--- a/sdk/python/src/zrok/environment/root.py
+++ b/sdk/python/src/zrok/environment/root.py
@@ -48,16 +48,16 @@ class Root:
 
         cfg = Configuration()
         cfg.host = apiEndpoint[0] + "/api/v1"
-        
+
         # Update: Configure authentication token
         # The token needs to be set with 'key' instead of 'x-token'
         # This matches the securityDefinitions in the OpenAPI spec
         cfg.api_key["key"] = self.env.Token
-        
+
         # Create the API client with the configured authentication
         auth_client = zrok.ApiClient(configuration=cfg)
         self.client_version_check(auth_client)
-        
+
         return auth_client
 
     def ApiEndpoint(self) -> ApiEndpoint:
diff --git a/sdk/python/src/zrok/share.py b/sdk/python/src/zrok/share.py
index 509e234d..f3c4a915 100644
--- a/sdk/python/src/zrok/share.py
+++ b/sdk/python/src/zrok/share.py
@@ -1,4 +1,3 @@
-from zrok import model
 from zrok_api.api import ShareApi
 from zrok.environment.root import Root
 from zrok_api.models.auth_user import AuthUser
@@ -58,7 +57,7 @@ def CreateShare(root: Root, request: model.ShareRequest) -> model.Share:
         zrok = root.Client()
     except Exception as e:
         raise Exception("error getting zrok client", e)
-    
+
     try:
         # Use share_with_http_info to get access to the HTTP info and handle custom response format
         share_api = ShareApi(zrok)
@@ -66,18 +65,18 @@ def CreateShare(root: Root, request: model.ShareRequest) -> model.Share:
         custom_headers = {
             'Accept': 'application/json, application/zrok.v1+json'
         }
-        
+
         response_data = share_api.share_with_http_info(
             body=out,
             _headers=custom_headers
         )
-        
+
         # Parse response
         if hasattr(response_data, 'data') and response_data.data is not None:
             res = response_data.data
         else:
             raise Exception("invalid response from server")
-            
+
     except ApiException as e:
         # If it's a content type error, try to parse the raw JSON
         if "Unsupported content type: application/zrok.v1+json" in str(e) and hasattr(e, 'body'):
@@ -85,11 +84,12 @@ def CreateShare(root: Root, request: model.ShareRequest) -> model.Share:
                 # Parse the response body directly
                 res_dict = json.loads(e.body)
                 # Create a response object with the expected fields
+
                 class ShareResponse:
                     def __init__(self, share_token, frontend_proxy_endpoints):
                         self.share_token = share_token
                         self.frontend_proxy_endpoints = frontend_proxy_endpoints
-                
+
                 res = ShareResponse(
                     share_token=res_dict.get('shareToken', ''),
                     frontend_proxy_endpoints=res_dict.get('frontendProxyEndpoints', [])
@@ -149,7 +149,7 @@ def DeleteShare(root: Root, shr: model.Share):
         custom_headers = {
             'Accept': 'application/json, application/zrok.v1+json'
         }
-        
+
         # Use unshare_with_http_info to get access to the HTTP info
         share_api.unshare_with_http_info(
             body=req,
@@ -182,7 +182,7 @@ def ReleaseReservedShare(root: Root, shr: model.Share):
         custom_headers = {
             'Accept': 'application/json, application/zrok.v1+json'
         }
-        
+
         # Use unshare_with_http_info to get access to the HTTP info
         share_api.unshare_with_http_info(
             body=req,