diff --git a/.github/workflows/dispatch_sdk_update.yml b/.github/workflows/dispatch_sdk_update.yml
deleted file mode 100644
index 4f1abb5c49..0000000000
--- a/.github/workflows/dispatch_sdk_update.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Update Immich SDK
-
-on:
-  workflow_dispatch:
-  push:
-    branches: ["main"]
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
-  cancel-in-progress: true
-
-jobs:
-  update-sdk-repos:
-    runs-on: ubuntu-latest
-    if: ${{ !github.event.pull_request.head.repo.fork }}
-    steps:
-      - uses: actions/github-script@v7
-        with:
-          github-token: ${{ secrets.GH_TOKEN }}
-          script: |
-            await github.rest.actions.createWorkflowDispatch({
-              owner: 'immich-app',
-              repo: 'immich-sdk-typescript-axios',
-              workflow_id: 'build.yml',
-              ref: 'main'
-            })
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
new file mode 100644
index 0000000000..cd3dbe2b10
--- /dev/null
+++ b/.github/workflows/sdk.yml
@@ -0,0 +1,31 @@
+name: Update Immich SDK
+
+on:
+  release:
+    types: [published]
+
+permissions:
+  packages: write
+
+jobs:
+  publish:
+    name: Publish `@immich/sdk`
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./open-api/typescript-sdk
+    steps:
+      - uses: actions/checkout@v4
+      # Setup .npmrc file to publish to npm
+      - uses: actions/setup-node@v4
+        with:
+          node-version: '20.x'
+          registry-url: 'https://registry.npmjs.org'
+      - name: Install deps
+        run: npm ci
+      - name: Build
+        run: npm run build
+      - name: Publish
+        run: npm publish
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/cli/package-lock.json b/cli/package-lock.json
index 2502656f73..82e920bd99 100644
--- a/cli/package-lock.json
+++ b/cli/package-lock.json
@@ -49,8 +49,10 @@
       "version": "1.98.2",
       "dev": true,
       "license": "GNU Affero General Public License version 3",
+      "dependencies": {
+        "@oazapfts/runtime": "^1.0.2"
+      },
       "devDependencies": {
-        "@oazapfts/runtime": "^1.0.0",
         "@types/node": "^20.11.0",
         "typescript": "^5.3.3"
       }
@@ -5186,7 +5188,7 @@
     "@immich/sdk": {
       "version": "file:../open-api/typescript-sdk",
       "requires": {
-        "@oazapfts/runtime": "^1.0.0",
+        "@oazapfts/runtime": "^1.0.2",
         "@types/node": "^20.11.0",
         "typescript": "^5.3.3"
       }
diff --git a/e2e/package-lock.json b/e2e/package-lock.json
index 51f51a1c65..f94958113c 100644
--- a/e2e/package-lock.json
+++ b/e2e/package-lock.json
@@ -38,7 +38,7 @@
     },
     "../cli": {
       "name": "@immich/cli",
-      "version": "2.0.8",
+      "version": "2.1.0",
       "dev": true,
       "license": "GNU Affero General Public License version 3",
       "dependencies": {
@@ -82,8 +82,10 @@
       "version": "1.98.2",
       "dev": true,
       "license": "GNU Affero General Public License version 3",
+      "dependencies": {
+        "@oazapfts/runtime": "^1.0.2"
+      },
       "devDependencies": {
-        "@oazapfts/runtime": "^1.0.0",
         "@types/node": "^20.11.0",
         "typescript": "^5.3.3"
       }
diff --git a/open-api/typescript-sdk/.npmignore b/open-api/typescript-sdk/.npmignore
new file mode 100644
index 0000000000..1898c3e21c
--- /dev/null
+++ b/open-api/typescript-sdk/.npmignore
@@ -0,0 +1,3 @@
+package-lock.json
+tsconfig.json
+src/
diff --git a/open-api/typescript-sdk/README.md b/open-api/typescript-sdk/README.md
new file mode 100644
index 0000000000..53a10eefc5
--- /dev/null
+++ b/open-api/typescript-sdk/README.md
@@ -0,0 +1,28 @@
+# @immich/sdk
+
+A TypeScript SDK for interfacing with the [Immich](https://immich.app/) API.
+
+## Install
+
+```bash
+npm i --save @immich/sdk
+```
+
+## Usage
+
+For a more detailed example, check out the [`@immich/cli`](https://github.com/immich-app/immich/tree/main/cli).
+
+```typescript
+import { defaults, getAllAlbums, getAllAssets, getMyUserInfo } from "@immich/sdk";
+
+const API_KEY = "<API_KEY>"; // process.env.IMMICH_API_KEY
+
+defaults.baseUrl = "https://demo.immich.app/api";
+defaults.headers = { "x-api-key": API_KEY };
+
+const user = await getMyUserInfo();
+const assets = await getAllAssets({ take: 1000 });
+const albums = await getAllAlbums({});
+
+console.log({ user, assets, albums });
+```
diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json
index 62412fdd47..0f1926996a 100644
--- a/open-api/typescript-sdk/package-lock.json
+++ b/open-api/typescript-sdk/package-lock.json
@@ -8,8 +8,10 @@
       "name": "@immich/sdk",
       "version": "1.98.2",
       "license": "GNU Affero General Public License version 3",
+      "dependencies": {
+        "@oazapfts/runtime": "^1.0.2"
+      },
       "devDependencies": {
-        "@oazapfts/runtime": "^1.0.0",
         "@types/node": "^20.11.0",
         "typescript": "^5.3.3"
       }
@@ -17,8 +19,7 @@
     "node_modules/@oazapfts/runtime": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/@oazapfts/runtime/-/runtime-1.0.2.tgz",
-      "integrity": "sha512-V33FjR6V+AkGRWYQW3XPm5BLn2loGl2ujSeja1TzdjjEn2zjGgl3ve0dcFf/jEwPZEOqQZl6YwIgIB/clXVqWw==",
-      "dev": true
+      "integrity": "sha512-V33FjR6V+AkGRWYQW3XPm5BLn2loGl2ujSeja1TzdjjEn2zjGgl3ve0dcFf/jEwPZEOqQZl6YwIgIB/clXVqWw=="
     },
     "node_modules/@types/node": {
       "version": "20.11.25",
diff --git a/open-api/typescript-sdk/package.json b/open-api/typescript-sdk/package.json
index ec5e78facb..4b04ee7c26 100644
--- a/open-api/typescript-sdk/package.json
+++ b/open-api/typescript-sdk/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@immich/sdk",
   "version": "1.98.2",
-  "description": "",
+  "description": "Auto-generated TypeScript SDK for the Immich API",
   "type": "module",
   "main": "./build/index.js",
   "types": "./build/index.d.ts",
@@ -15,9 +15,16 @@
     "build": "tsc"
   },
   "license": "GNU Affero General Public License version 3",
+  "dependencies": {
+    "@oazapfts/runtime": "^1.0.2"
+  },
   "devDependencies": {
-    "@oazapfts/runtime": "^1.0.0",
     "@types/node": "^20.11.0",
     "typescript": "^5.3.3"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/immich-app/immich.git",
+    "directory": "open-api/typescript-sdk"
   }
 }
diff --git a/open-api/typescript-sdk/src/index.ts b/open-api/typescript-sdk/src/index.ts
index 5759e66ad9..d81c7282ac 100644
--- a/open-api/typescript-sdk/src/index.ts
+++ b/open-api/typescript-sdk/src/index.ts
@@ -1,2 +1,2 @@
-export * from './fetch-client';
-export * from './fetch-errors';
+export * from './fetch-client.js';
+export * from './fetch-errors.js';
diff --git a/open-api/typescript-sdk/tsconfig.json b/open-api/typescript-sdk/tsconfig.json
index 251202774e..30d58d5177 100644
--- a/open-api/typescript-sdk/tsconfig.json
+++ b/open-api/typescript-sdk/tsconfig.json
@@ -5,8 +5,8 @@
     "skipLibCheck": true,
     "declaration": true,
     "outDir": "build",
-    "module": "esnext",
-    "moduleResolution": "Bundler",
+    "module": "Node16",
+    "moduleResolution": "Node16",
     "lib": ["esnext", "dom"]
   },
   "include": ["src/**/*.ts"]
diff --git a/web/package-lock.json b/web/package-lock.json
index 5d8212d3ee..26c1e10364 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -65,8 +65,10 @@
       "name": "@immich/sdk",
       "version": "1.98.2",
       "license": "GNU Affero General Public License version 3",
+      "dependencies": {
+        "@oazapfts/runtime": "^1.0.2"
+      },
       "devDependencies": {
-        "@oazapfts/runtime": "^1.0.0",
         "@types/node": "^20.11.0",
         "typescript": "^5.3.3"
       }