diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 43273caf361d53bed6488c4b75e1405ad6e7bd7f..40c0215de4b6ddbb83084761a19bb4dc88473471 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,6 +19,7 @@ lint:
     - prepare
   script:
     - npm run lint
+    - npm run type-check
 
 build:
   stage: build
diff --git a/index.html b/index.html
index 6ff0a1461d0d1a572ff8011c9bd4aeca2c5749fc..821596ca95408ab9601050d24f8f38e6163dbf94 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
   <head>
     <meta charset="UTF-8" />
     <link rel="icon" href="/favicon.ico" />
-    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>S3 Proxy</title>
@@ -11,6 +11,6 @@
   <body>
     <div id="app"></div>
     <script type="module" src="/src/main.ts"></script>
-    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
   </body>
 </html>
diff --git a/package-lock.json b/package-lock.json
index ef9b13f01e3f1db36ad6deb386dc08dbe17b2cb9..1e2aa7ba92f47efa04d3295a92616e3a77c58774 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,8 @@
       "version": "0.0.0",
       "dependencies": {
         "@aws-sdk/client-s3": "^3.131.0",
-        "bootstrap": "^5.1.3",
+        "axios": "^0.27.2",
+        "bootstrap": "^5.2.0",
         "bootstrap-icons": "^1.9.1",
         "pinia": "^2.0.16",
         "vue": "^3.2.37",
@@ -2169,6 +2170,20 @@
         "node": ">=8"
       }
     },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+      "dependencies": {
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2182,15 +2197,21 @@
       "dev": true
     },
     "node_modules/bootstrap": {
-      "version": "5.1.3",
-      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
-      "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/bootstrap"
-      },
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz",
+      "integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/twbs"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/bootstrap"
+        }
+      ],
       "peerDependencies": {
-        "@popperjs/core": "^2.10.2"
+        "@popperjs/core": "^2.11.5"
       }
     },
     "node_modules/bootstrap-icons": {
@@ -2299,6 +2320,17 @@
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "dev": true
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/commander": {
       "version": "9.4.0",
       "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz",
@@ -2384,6 +2416,14 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -3218,6 +3258,38 @@
       "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==",
       "dev": true
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+      "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/fs-extra": {
       "version": "10.1.0",
       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -3905,6 +3977,25 @@
         "node": ">=8.6"
       }
     },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -7107,6 +7198,20 @@
       "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
       "dev": true
     },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "axios": {
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+      "requires": {
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
+      }
+    },
     "balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -7120,9 +7225,9 @@
       "dev": true
     },
     "bootstrap": {
-      "version": "5.1.3",
-      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
-      "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz",
+      "integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==",
       "requires": {}
     },
     "bootstrap-icons": {
@@ -7207,6 +7312,14 @@
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "dev": true
     },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
     "commander": {
       "version": "9.4.0",
       "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz",
@@ -7266,6 +7379,11 @@
         "object-keys": "^1.1.1"
       }
     },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+    },
     "dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -7797,6 +7915,21 @@
       "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==",
       "dev": true
     },
+    "follow-redirects": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+      "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
+    },
+    "form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      }
+    },
     "fs-extra": {
       "version": "10.1.0",
       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -8293,6 +8426,19 @@
         "picomatch": "^2.3.1"
       }
     },
+    "mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+    },
+    "mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "requires": {
+        "mime-db": "1.52.0"
+      }
+    },
     "minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
diff --git a/package.json b/package.json
index 78fd112981ad532033699b7833c67c790cdc72bd..6452065bba865ff368b1105e888bf4e0c82ddf4d 100644
--- a/package.json
+++ b/package.json
@@ -8,11 +8,11 @@
     "build-only": "vite build",
     "type-check": "vue-tsc --noEmit",
     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
-    "generate-client": "openapi --input http://localhost:8000/api/openapi.json --output src/client --client axios"
+    "generate-client": "openapi --input http://localhost:9999/api/openapi.json --output src/client --client axios"
   },
   "dependencies": {
     "@aws-sdk/client-s3": "^3.131.0",
-    "bootstrap": "^5.1.3",
+    "bootstrap": "^5.2.0",
     "bootstrap-icons": "^1.9.1",
     "pinia": "^2.0.16",
     "vue": "^3.2.37",
@@ -29,10 +29,11 @@
     "eslint": "^8.5.0",
     "eslint-plugin-vue": "^9.0.0",
     "npm-run-all": "^4.1.5",
+    "openapi-typescript-codegen": "^0.23.0",
     "prettier": "^2.5.1",
     "typescript": "~4.7.4",
     "vite": "^3.0.1",
     "vue-tsc": "^0.38.8",
-    "openapi-typescript-codegen": "^0.23.0"
+    "axios": "^0.27.2"
   }
 }
diff --git a/src/client/core/ApiError.ts b/src/client/core/ApiError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99d792996765118614adf6a232bc8766b95710e9
--- /dev/null
+++ b/src/client/core/ApiError.ts
@@ -0,0 +1,24 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ApiRequestOptions } from './ApiRequestOptions';
+import type { ApiResult } from './ApiResult';
+
+export class ApiError extends Error {
+    public readonly url: string;
+    public readonly status: number;
+    public readonly statusText: string;
+    public readonly body: any;
+    public readonly request: ApiRequestOptions;
+
+    constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
+        super(message);
+
+        this.name = 'ApiError';
+        this.url = response.url;
+        this.status = response.status;
+        this.statusText = response.statusText;
+        this.body = response.body;
+        this.request = request;
+    }
+}
diff --git a/src/client/core/ApiRequestOptions.ts b/src/client/core/ApiRequestOptions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7b77538c5ce6aa48f141541392121fbcafc4821
--- /dev/null
+++ b/src/client/core/ApiRequestOptions.ts
@@ -0,0 +1,16 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type ApiRequestOptions = {
+    readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
+    readonly url: string;
+    readonly path?: Record<string, any>;
+    readonly cookies?: Record<string, any>;
+    readonly headers?: Record<string, any>;
+    readonly query?: Record<string, any>;
+    readonly formData?: Record<string, any>;
+    readonly body?: any;
+    readonly mediaType?: string;
+    readonly responseHeader?: string;
+    readonly errors?: Record<number, string>;
+};
diff --git a/src/client/core/ApiResult.ts b/src/client/core/ApiResult.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b095dc7708af2aa405a6dc7ad08f423334bc7f24
--- /dev/null
+++ b/src/client/core/ApiResult.ts
@@ -0,0 +1,10 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type ApiResult = {
+    readonly url: string;
+    readonly ok: boolean;
+    readonly status: number;
+    readonly statusText: string;
+    readonly body: any;
+};
diff --git a/src/client/core/CancelablePromise.ts b/src/client/core/CancelablePromise.ts
new file mode 100644
index 0000000000000000000000000000000000000000..26ad303915af161f170cc144a5e7684be3053778
--- /dev/null
+++ b/src/client/core/CancelablePromise.ts
@@ -0,0 +1,128 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export class CancelError extends Error {
+
+    constructor(message: string) {
+        super(message);
+        this.name = 'CancelError';
+    }
+
+    public get isCancelled(): boolean {
+        return true;
+    }
+}
+
+export interface OnCancel {
+    readonly isResolved: boolean;
+    readonly isRejected: boolean;
+    readonly isCancelled: boolean;
+
+    (cancelHandler: () => void): void;
+}
+
+export class CancelablePromise<T> implements Promise<T> {
+    readonly [Symbol.toStringTag]!: string;
+
+    private _isResolved: boolean;
+    private _isRejected: boolean;
+    private _isCancelled: boolean;
+    private readonly _cancelHandlers: (() => void)[];
+    private readonly _promise: Promise<T>;
+    private _resolve?: (value: T | PromiseLike<T>) => void;
+    private _reject?: (reason?: any) => void;
+
+    constructor(
+        executor: (
+            resolve: (value: T | PromiseLike<T>) => void,
+            reject: (reason?: any) => void,
+            onCancel: OnCancel
+        ) => void
+    ) {
+        this._isResolved = false;
+        this._isRejected = false;
+        this._isCancelled = false;
+        this._cancelHandlers = [];
+        this._promise = new Promise<T>((resolve, reject) => {
+            this._resolve = resolve;
+            this._reject = reject;
+
+            const onResolve = (value: T | PromiseLike<T>): void => {
+                if (this._isResolved || this._isRejected || this._isCancelled) {
+                    return;
+                }
+                this._isResolved = true;
+                this._resolve?.(value);
+            };
+
+            const onReject = (reason?: any): void => {
+                if (this._isResolved || this._isRejected || this._isCancelled) {
+                    return;
+                }
+                this._isRejected = true;
+                this._reject?.(reason);
+            };
+
+            const onCancel = (cancelHandler: () => void): void => {
+                if (this._isResolved || this._isRejected || this._isCancelled) {
+                    return;
+                }
+                this._cancelHandlers.push(cancelHandler);
+            };
+
+            Object.defineProperty(onCancel, 'isResolved', {
+                get: (): boolean => this._isResolved,
+            });
+
+            Object.defineProperty(onCancel, 'isRejected', {
+                get: (): boolean => this._isRejected,
+            });
+
+            Object.defineProperty(onCancel, 'isCancelled', {
+                get: (): boolean => this._isCancelled,
+            });
+
+            return executor(onResolve, onReject, onCancel as OnCancel);
+        });
+    }
+
+    public then<TResult1 = T, TResult2 = never>(
+        onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
+        onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
+    ): Promise<TResult1 | TResult2> {
+        return this._promise.then(onFulfilled, onRejected);
+    }
+
+    public catch<TResult = never>(
+        onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
+    ): Promise<T | TResult> {
+        return this._promise.catch(onRejected);
+    }
+
+    public finally(onFinally?: (() => void) | null): Promise<T> {
+        return this._promise.finally(onFinally);
+    }
+
+    public cancel(): void {
+        if (this._isResolved || this._isRejected || this._isCancelled) {
+            return;
+        }
+        this._isCancelled = true;
+        if (this._cancelHandlers.length) {
+            try {
+                for (const cancelHandler of this._cancelHandlers) {
+                    cancelHandler();
+                }
+            } catch (error) {
+                console.warn('Cancellation threw an error', error);
+                return;
+            }
+        }
+        this._cancelHandlers.length = 0;
+        this._reject?.(new CancelError('Request aborted'));
+    }
+
+    public get isCancelled(): boolean {
+        return this._isCancelled;
+    }
+}
diff --git a/src/client/core/OpenAPI.ts b/src/client/core/OpenAPI.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dba8bdb3010f83c6abfb91a17471cbb60d40a938
--- /dev/null
+++ b/src/client/core/OpenAPI.ts
@@ -0,0 +1,31 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ApiRequestOptions } from './ApiRequestOptions';
+
+type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
+type Headers = Record<string, string>;
+
+export type OpenAPIConfig = {
+    BASE: string;
+    VERSION: string;
+    WITH_CREDENTIALS: boolean;
+    CREDENTIALS: 'include' | 'omit' | 'same-origin';
+    TOKEN?: string | Resolver<string>;
+    USERNAME?: string | Resolver<string>;
+    PASSWORD?: string | Resolver<string>;
+    HEADERS?: Headers | Resolver<Headers>;
+    ENCODE_PATH?: (path: string) => string;
+};
+
+export const OpenAPI: OpenAPIConfig = {
+    BASE: 'http://localhost:9999/api',
+    VERSION: '1.0.0',
+    WITH_CREDENTIALS: false,
+    CREDENTIALS: 'include',
+    TOKEN: undefined,
+    USERNAME: undefined,
+    PASSWORD: undefined,
+    HEADERS: undefined,
+    ENCODE_PATH: undefined,
+};
diff --git a/src/client/core/request.ts b/src/client/core/request.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b5375ab44ec20863646a5debca820b42c2b7500
--- /dev/null
+++ b/src/client/core/request.ts
@@ -0,0 +1,304 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import axios from 'axios';
+import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
+import FormData from 'form-data';
+
+import { ApiError } from './ApiError';
+import type { ApiRequestOptions } from './ApiRequestOptions';
+import type { ApiResult } from './ApiResult';
+import { CancelablePromise } from './CancelablePromise';
+import type { OnCancel } from './CancelablePromise';
+import type { OpenAPIConfig } from './OpenAPI';
+
+const isDefined = <T>(value: T | null | undefined): value is Exclude<T, null | undefined> => {
+    return value !== undefined && value !== null;
+};
+
+const isString = (value: any): value is string => {
+    return typeof value === 'string';
+};
+
+const isStringWithValue = (value: any): value is string => {
+    return isString(value) && value !== '';
+};
+
+const isBlob = (value: any): value is Blob => {
+    return (
+        typeof value === 'object' &&
+        typeof value.type === 'string' &&
+        typeof value.stream === 'function' &&
+        typeof value.arrayBuffer === 'function' &&
+        typeof value.constructor === 'function' &&
+        typeof value.constructor.name === 'string' &&
+        /^(Blob|File)$/.test(value.constructor.name) &&
+        /^(Blob|File)$/.test(value[Symbol.toStringTag])
+    );
+};
+
+const isFormData = (value: any): value is FormData => {
+    return value instanceof FormData;
+};
+
+const isSuccess = (status: number): boolean => {
+    return status >= 200 && status < 300;
+};
+
+const base64 = (str: string): string => {
+    try {
+        return btoa(str);
+    } catch (err) {
+        // @ts-ignore
+        return Buffer.from(str).toString('base64');
+    }
+};
+
+const getQueryString = (params: Record<string, any>): string => {
+    const qs: string[] = [];
+
+    const append = (key: string, value: any) => {
+        qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
+    };
+
+    const process = (key: string, value: any) => {
+        if (isDefined(value)) {
+            if (Array.isArray(value)) {
+                value.forEach(v => {
+                    process(key, v);
+                });
+            } else if (typeof value === 'object') {
+                Object.entries(value).forEach(([k, v]) => {
+                    process(`${key}[${k}]`, v);
+                });
+            } else {
+                append(key, value);
+            }
+        }
+    };
+
+    Object.entries(params).forEach(([key, value]) => {
+        process(key, value);
+    });
+
+    if (qs.length > 0) {
+        return `?${qs.join('&')}`;
+    }
+
+    return '';
+};
+
+const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
+    const encoder = config.ENCODE_PATH || encodeURI;
+
+    const path = options.url
+        .replace('{api-version}', config.VERSION)
+        .replace(/{(.*?)}/g, (substring: string, group: string) => {
+            if (options.path?.hasOwnProperty(group)) {
+                return encoder(String(options.path[group]));
+            }
+            return substring;
+        });
+
+    const url = `${config.BASE}${path}`;
+    if (options.query) {
+        return `${url}${getQueryString(options.query)}`;
+    }
+    return url;
+};
+
+const getFormData = (options: ApiRequestOptions): FormData | undefined => {
+    if (options.formData) {
+        const formData = new FormData();
+
+        const process = (key: string, value: any) => {
+            if (isString(value) || isBlob(value)) {
+                formData.append(key, value);
+            } else {
+                formData.append(key, JSON.stringify(value));
+            }
+        };
+
+        Object.entries(options.formData)
+            .filter(([_, value]) => isDefined(value))
+            .forEach(([key, value]) => {
+                if (Array.isArray(value)) {
+                    value.forEach(v => process(key, v));
+                } else {
+                    process(key, value);
+                }
+            });
+
+        return formData;
+    }
+    return undefined;
+};
+
+type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
+
+const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Resolver<T>): Promise<T | undefined> => {
+    if (typeof resolver === 'function') {
+        return (resolver as Resolver<T>)(options);
+    }
+    return resolver;
+};
+
+const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise<Record<string, string>> => {
+    const token = await resolve(options, config.TOKEN);
+    const username = await resolve(options, config.USERNAME);
+    const password = await resolve(options, config.PASSWORD);
+    const additionalHeaders = await resolve(options, config.HEADERS);
+    const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {}
+
+    const headers = Object.entries({
+        Accept: 'application/json',
+        ...additionalHeaders,
+        ...options.headers,
+        ...formHeaders,
+    })
+    .filter(([_, value]) => isDefined(value))
+    .reduce((headers, [key, value]) => ({
+        ...headers,
+        [key]: String(value),
+    }), {} as Record<string, string>);
+
+    if (isStringWithValue(token)) {
+        headers['Authorization'] = `Bearer ${token}`;
+    }
+
+    if (isStringWithValue(username) && isStringWithValue(password)) {
+        const credentials = base64(`${username}:${password}`);
+        headers['Authorization'] = `Basic ${credentials}`;
+    }
+
+    if (options.body) {
+        if (options.mediaType) {
+            headers['Content-Type'] = options.mediaType;
+        } else if (isBlob(options.body)) {
+            headers['Content-Type'] = options.body.type || 'application/octet-stream';
+        } else if (isString(options.body)) {
+            headers['Content-Type'] = 'text/plain';
+        } else if (!isFormData(options.body)) {
+            headers['Content-Type'] = 'application/json';
+        }
+    }
+
+    return headers;
+};
+
+const getRequestBody = (options: ApiRequestOptions): any => {
+    if (options.body) {
+        return options.body;
+    }
+    return undefined;
+};
+
+const sendRequest = async <T>(
+    config: OpenAPIConfig,
+    options: ApiRequestOptions,
+    url: string,
+    body: any,
+    formData: FormData | undefined,
+    headers: Record<string, string>,
+    onCancel: OnCancel
+): Promise<AxiosResponse<T>> => {
+    const source = axios.CancelToken.source();
+
+    const requestConfig: AxiosRequestConfig = {
+        url,
+        headers,
+        data: body ?? formData,
+        method: options.method,
+        withCredentials: config.WITH_CREDENTIALS,
+        cancelToken: source.token,
+    };
+
+    onCancel(() => source.cancel('The user aborted a request.'));
+
+    try {
+        return await axios.request(requestConfig);
+    } catch (error) {
+        const axiosError = error as AxiosError<T>;
+        if (axiosError.response) {
+            return axiosError.response;
+        }
+        throw error;
+    }
+};
+
+const getResponseHeader = (response: AxiosResponse<any>, responseHeader?: string): string | undefined => {
+    if (responseHeader) {
+        const content = response.headers[responseHeader];
+        if (isString(content)) {
+            return content;
+        }
+    }
+    return undefined;
+};
+
+const getResponseBody = (response: AxiosResponse<any>): any => {
+    if (response.status !== 204) {
+        return response.data;
+    }
+    return undefined;
+};
+
+const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
+    const errors: Record<number, string> = {
+        400: 'Bad Request',
+        401: 'Unauthorized',
+        403: 'Forbidden',
+        404: 'Not Found',
+        500: 'Internal Server Error',
+        502: 'Bad Gateway',
+        503: 'Service Unavailable',
+        ...options.errors,
+    }
+
+    const error = errors[result.status];
+    if (error) {
+        throw new ApiError(options, result, error);
+    }
+
+    if (!result.ok) {
+        throw new ApiError(options, result, 'Generic Error');
+    }
+};
+
+/**
+ * Request method
+ * @param config The OpenAPI configuration object
+ * @param options The request options from the service
+ * @returns CancelablePromise<T>
+ * @throws ApiError
+ */
+export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise<T> => {
+    return new CancelablePromise(async (resolve, reject, onCancel) => {
+        try {
+            const url = getUrl(config, options);
+            const formData = getFormData(options);
+            const body = getRequestBody(options);
+            const headers = await getHeaders(config, options, formData);
+
+            if (!onCancel.isCancelled) {
+                const response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel);
+                const responseBody = getResponseBody(response);
+                const responseHeader = getResponseHeader(response, options.responseHeader);
+
+                const result: ApiResult = {
+                    url,
+                    ok: isSuccess(response.status),
+                    status: response.status,
+                    statusText: response.statusText,
+                    body: responseHeader ?? responseBody,
+                };
+
+                catchErrorCodes(options, result);
+
+                resolve(result.body);
+            }
+        } catch (error) {
+            reject(error);
+        }
+    });
+};
diff --git a/src/client/index.ts b/src/client/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f41f3e7ede88b28944d1b09ce2271ac74f81662
--- /dev/null
+++ b/src/client/index.ts
@@ -0,0 +1,27 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export { ApiError } from './core/ApiError';
+export { CancelablePromise, CancelError } from './core/CancelablePromise';
+export { OpenAPI } from './core/OpenAPI';
+export type { OpenAPIConfig } from './core/OpenAPI';
+
+export type { BucketIn } from './models/BucketIn';
+export type { BucketOut } from './models/BucketOut';
+export type { BucketPermission } from './models/BucketPermission';
+export type { BucketPermissionParameters } from './models/BucketPermissionParameters';
+export type { ErrorDetail } from './models/ErrorDetail';
+export type { HTTPValidationError } from './models/HTTPValidationError';
+export { PermissionEnum } from './models/PermissionEnum';
+export type { S3Key } from './models/S3Key';
+export type { S3ObjectMetaInformation } from './models/S3ObjectMetaInformation';
+export type { User } from './models/User';
+export type { ValidationError } from './models/ValidationError';
+
+export { AuthService } from './services/AuthService';
+export { BucketService } from './services/BucketService';
+export { BucketPermissionsService } from './services/BucketPermissionsService';
+export { KeyService } from './services/KeyService';
+export { MiscellaneousService } from './services/MiscellaneousService';
+export { ObjectService } from './services/ObjectService';
+export { UserService } from './services/UserService';
diff --git a/src/client/models/BucketIn.ts b/src/client/models/BucketIn.ts
new file mode 100644
index 0000000000000000000000000000000000000000..530031e6c9db2eed482ac3b2a05f512112fdcb3e
--- /dev/null
+++ b/src/client/models/BucketIn.ts
@@ -0,0 +1,18 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+/**
+ * Schema for creating a new bucket.
+ */
+export type BucketIn = {
+    /**
+     * Name of the bucket
+     */
+    name: string;
+    /**
+     * Description of the bucket
+     */
+    description: string;
+};
+
diff --git a/src/client/models/BucketOut.ts b/src/client/models/BucketOut.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d3c68512a9a8cee4ca8681ee5fa4af98fe7a8268
--- /dev/null
+++ b/src/client/models/BucketOut.ts
@@ -0,0 +1,26 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+/**
+ * Schema for answering a request with a bucket.
+ */
+export type BucketOut = {
+    /**
+     * Name of the bucket
+     */
+    name: string;
+    /**
+     * Description of the bucket
+     */
+    description: string;
+    /**
+     * Time when the bucket was created
+     */
+    created_at: string;
+    /**
+     * Username of the owner
+     */
+    owner: string;
+};
+
diff --git a/src/client/models/BucketPermission.ts b/src/client/models/BucketPermission.ts
new file mode 100644
index 0000000000000000000000000000000000000000..78679591d26554041688efc915bbc8c1a3d1e113
--- /dev/null
+++ b/src/client/models/BucketPermission.ts
@@ -0,0 +1,36 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+import type { PermissionEnum } from './PermissionEnum';
+
+/**
+ * Schema for the bucket permissions.
+ */
+export type BucketPermission = {
+    /**
+     * Start date of permission
+     */
+    from_timestamp?: string;
+    /**
+     * End date of permission
+     */
+    to_timestamp?: string;
+    /**
+     * Prefix of subfolder
+     */
+    file_prefix?: string;
+    /**
+     * Permission
+     */
+    permission?: (PermissionEnum | string);
+    /**
+     * Name of User
+     */
+    username: string;
+    /**
+     * Name of Bucket
+     */
+    bucket_name: string;
+};
+
diff --git a/src/client/models/BucketPermissionParameters.ts b/src/client/models/BucketPermissionParameters.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6fb78dd8d4861faddaef3191fd8b3c91bc690dd2
--- /dev/null
+++ b/src/client/models/BucketPermissionParameters.ts
@@ -0,0 +1,28 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+import type { PermissionEnum } from './PermissionEnum';
+
+/**
+ * Schema for the parameters of a bucket permission.
+ */
+export type BucketPermissionParameters = {
+    /**
+     * Start date of permission
+     */
+    from_timestamp?: string;
+    /**
+     * End date of permission
+     */
+    to_timestamp?: string;
+    /**
+     * Prefix of subfolder
+     */
+    file_prefix?: string;
+    /**
+     * Permission
+     */
+    permission?: (PermissionEnum | string);
+};
+
diff --git a/src/client/models/ErrorDetail.ts b/src/client/models/ErrorDetail.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b01e5aa28734c901620e98f8795e9528004d1513
--- /dev/null
+++ b/src/client/models/ErrorDetail.ts
@@ -0,0 +1,14 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+/**
+ * Schema for a error due to a rejected request.
+ */
+export type ErrorDetail = {
+    /**
+     * Detail about the occurred error
+     */
+    detail: string;
+};
+
diff --git a/src/client/models/HTTPValidationError.ts b/src/client/models/HTTPValidationError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8c21354123500873c7548194a36421eb8316c78
--- /dev/null
+++ b/src/client/models/HTTPValidationError.ts
@@ -0,0 +1,10 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+import type { ValidationError } from './ValidationError';
+
+export type HTTPValidationError = {
+    detail?: Array<ValidationError>;
+};
+
diff --git a/src/client/models/PermissionEnum.ts b/src/client/models/PermissionEnum.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ae0d72869da4a2291f3362cae30e2376b46af3f9
--- /dev/null
+++ b/src/client/models/PermissionEnum.ts
@@ -0,0 +1,12 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+/**
+ * Enumeration for the possible permission on a bucket.
+ */
+export enum PermissionEnum {
+    READ = 'READ',
+    WRITE = 'WRITE',
+    READWRITE = 'READWRITE',
+}
diff --git a/src/client/models/S3Key.ts b/src/client/models/S3Key.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5b3d7909c65cfa03f76252074b0b68bebd41826c
--- /dev/null
+++ b/src/client/models/S3Key.ts
@@ -0,0 +1,22 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+/**
+ * Schema for a S3 key associated with a user.
+ */
+export type S3Key = {
+    /**
+     * Username of the user of that access key
+     */
+    user: string;
+    /**
+     * ID of the S3 access key
+     */
+    access_key: string;
+    /**
+     * Secret of the S3 access key
+     */
+    secret_key: string;
+};
+
diff --git a/src/client/models/S3ObjectMetaInformation.ts b/src/client/models/S3ObjectMetaInformation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4292c7e82fe0982d788527cd355e4c6f0595ebc3
--- /dev/null
+++ b/src/client/models/S3ObjectMetaInformation.ts
@@ -0,0 +1,26 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+/**
+ * Schema for the meta-information about a S3 object.
+ */
+export type S3ObjectMetaInformation = {
+    /**
+     * Key of the Object in the S3 store
+     */
+    key: string;
+    /**
+     * Name of the Bucket in which the object is
+     */
+    bucket: string;
+    /**
+     * Size of the object in Bytes
+     */
+    size: number;
+    /**
+     * Last time the object was modified
+     */
+    last_modified: string;
+};
+
diff --git a/src/client/models/User.ts b/src/client/models/User.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58137cd888487ba4aff980746431f0e8c35e943c
--- /dev/null
+++ b/src/client/models/User.ts
@@ -0,0 +1,22 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+/**
+ * Schema for a user.
+ */
+export type User = {
+    /**
+     * ID of the user
+     */
+    uid: string;
+    /**
+     * Username of the user
+     */
+    username: string;
+    /**
+     * Full Name of the user
+     */
+    display_name: string;
+};
+
diff --git a/src/client/models/ValidationError.ts b/src/client/models/ValidationError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..16dc86a6807d9ad1f7e9d06638d4bd002682dc42
--- /dev/null
+++ b/src/client/models/ValidationError.ts
@@ -0,0 +1,10 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+export type ValidationError = {
+    loc: Array<(string | number)>;
+    msg: string;
+    type: string;
+};
+
diff --git a/src/client/services/AuthService.ts b/src/client/services/AuthService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a4680da8ea0e3d7b2a02aca271961c34c424ec3
--- /dev/null
+++ b/src/client/services/AuthService.ts
@@ -0,0 +1,36 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+
+export class AuthService {
+
+    /**
+     * Life Science Login Callback
+     * Callback for the Life Science Identity Provider.
+     *
+     * To start the login process visit the route [login route](/api/auth/login/)
+     *
+     * If the user is already known to the system, then a JWT token will be created and sent via the 'set-cookie' header.
+     * The key for this Cookie is 'bearer'.
+     *
+     * If the user is new, he will be created and then a JWT token is issued.
+     *
+     * This JWT has to be sent to all authorized endpoints via the HTTPBearer scheme.
+     *
+     * @returns void
+     * @throws ApiError
+     */
+    public static authLoginCallback(): CancelablePromise<void> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/auth/callback',
+            errors: {
+                302: `Successful Response`,
+            },
+        });
+    }
+
+}
diff --git a/src/client/services/BucketPermissionsService.ts b/src/client/services/BucketPermissionsService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..871549bd7b3a928aa0332c178ca5d6e70f3da0b1
--- /dev/null
+++ b/src/client/services/BucketPermissionsService.ts
@@ -0,0 +1,185 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { BucketPermission } from '../models/BucketPermission';
+import type { BucketPermissionParameters } from '../models/BucketPermissionParameters';
+
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+
+export class BucketPermissionsService {
+
+    /**
+     * Get permission for bucket and user combination.
+     * Get the bucket permissions for the specific combination of bucket and user.
+     *
+     * The owner of the bucket and the grantee of the permission can view it.
+     *
+     * @param bucketName Name of bucket
+     * @param username Username of a user
+     * @returns BucketPermission Successful Response
+     * @throws ApiError
+     */
+    public static bucketPermissionsGetPermissionForBucket(
+        bucketName: string,
+        username: string,
+    ): CancelablePromise<BucketPermission> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/permissions/bucket/{bucket_name}/user/{username}',
+            path: {
+                'bucket_name': bucketName,
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Update a bucket permission
+     * Update a permission for a bucket and user.
+     *
+     * @param username Username of a user
+     * @param bucketName Name of bucket
+     * @param requestBody
+     * @returns BucketPermission Successful Response
+     * @throws ApiError
+     */
+    public static bucketPermissionsUpdatePermission(
+        username: string,
+        bucketName: string,
+        requestBody: BucketPermissionParameters,
+    ): CancelablePromise<BucketPermission> {
+        return __request(OpenAPI, {
+            method: 'PUT',
+            url: '/permissions/bucket/{bucket_name}/user/{username}',
+            path: {
+                'username': username,
+                'bucket_name': bucketName,
+            },
+            body: requestBody,
+            mediaType: 'application/json',
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Delete a bucket permission
+     * Delete the bucket permissions for the specific combination of bucket and user.
+     *
+     * The owner of the bucket and the grantee of the permission can delete it.
+     *
+     * @param bucketName Name of bucket
+     * @param username Username of a user
+     * @returns void
+     * @throws ApiError
+     */
+    public static bucketPermissionsDeletePermissionForBucket(
+        bucketName: string,
+        username: string,
+    ): CancelablePromise<void> {
+        return __request(OpenAPI, {
+            method: 'DELETE',
+            url: '/permissions/bucket/{bucket_name}/user/{username}',
+            path: {
+                'bucket_name': bucketName,
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get all permissions for a bucket.
+     * List all the bucket permissions for the given bucket.
+     *
+     * @param bucketName Name of bucket
+     * @returns BucketPermission Successful Response
+     * @throws ApiError
+     */
+    public static bucketPermissionsListPermissionsPerBucket(
+        bucketName: string,
+    ): CancelablePromise<Array<BucketPermission>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/permissions/bucket/{bucket_name}',
+            path: {
+                'bucket_name': bucketName,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get all permissions for a user.
+     * List all the bucket permissions for the given user.
+     *
+     * @param username Username of a user
+     * @returns BucketPermission Successful Response
+     * @throws ApiError
+     */
+    public static bucketPermissionsListPermissionsPerUser(
+        username: string,
+    ): CancelablePromise<Array<BucketPermission>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/permissions/user/{username}',
+            path: {
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Create a permission.
+     * Create a permission for a bucket and user.
+     *
+     * @param requestBody
+     * @returns BucketPermission Successful Response
+     * @throws ApiError
+     */
+    public static bucketPermissionsCreatePermission(
+        requestBody: BucketPermission,
+    ): CancelablePromise<BucketPermission> {
+        return __request(OpenAPI, {
+            method: 'POST',
+            url: '/permissions/',
+            body: requestBody,
+            mediaType: 'application/json',
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+}
diff --git a/src/client/services/BucketService.ts b/src/client/services/BucketService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0b4457b9d0f110dcf92600a17780e821b60538c
--- /dev/null
+++ b/src/client/services/BucketService.ts
@@ -0,0 +1,173 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { BucketIn } from '../models/BucketIn';
+import type { BucketOut } from '../models/BucketOut';
+import type { S3ObjectMetaInformation } from '../models/S3ObjectMetaInformation';
+
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+
+export class BucketService {
+
+    /**
+     * List buckets of user
+     * List the buckets of the current user where the user has READ permissions for.
+     *
+     * @returns BucketOut Successful Response
+     * @throws ApiError
+     */
+    public static bucketListBuckets(): CancelablePromise<Array<BucketOut>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/buckets/',
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+            },
+        });
+    }
+
+    /**
+     * Create a bucket for the current user
+     * Create a bucket for the current user.
+     *
+     * The name of the bucket has some constraints.
+     * For more information see the [Ceph documentation](https://docs.ceph.com/en/quincy/radosgw/s3/bucketops/#constraints)
+     *
+     * @param requestBody
+     * @returns BucketOut Successful Response
+     * @throws ApiError
+     */
+    public static bucketCreateBucket(
+        requestBody: BucketIn,
+    ): CancelablePromise<BucketOut> {
+        return __request(OpenAPI, {
+            method: 'POST',
+            url: '/buckets/',
+            body: requestBody,
+            mediaType: 'application/json',
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get a bucket by its name
+     * Get a bucket by its name if the current user has READ permissions for the bucket.
+     *
+     * @param bucketName Name of bucket
+     * @returns BucketOut Successful Response
+     * @throws ApiError
+     */
+    public static bucketGetBucket(
+        bucketName: string,
+    ): CancelablePromise<BucketOut> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/buckets/{bucket_name}',
+            path: {
+                'bucket_name': bucketName,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Delete a bucket
+     * Delete a bucket by its name. Only the owner of the bucket can delete the bucket.
+     *
+     * @param bucketName Name of bucket
+     * @param forceDelete Delete even non-empty bucket
+     * @returns void
+     * @throws ApiError
+     */
+    public static bucketDeleteBucket(
+        bucketName: string,
+        forceDelete: boolean = false,
+    ): CancelablePromise<void> {
+        return __request(OpenAPI, {
+            method: 'DELETE',
+            url: '/buckets/{bucket_name}',
+            path: {
+                'bucket_name': bucketName,
+            },
+            query: {
+                'force_delete': forceDelete,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get the metadata of the objects in the bucket
+     * Get the metadata of the objects in the bucket.
+     *
+     * @param bucketName Name of bucket
+     * @returns S3ObjectMetaInformation Successful Response
+     * @throws ApiError
+     */
+    public static objectGetBucketObjects(
+        bucketName: string,
+    ): CancelablePromise<Array<S3ObjectMetaInformation>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/buckets/{bucket_name}/objects',
+            path: {
+                'bucket_name': bucketName,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get the metadata about a specific object
+     * Get the metadata of a specific object in a bucket.
+     *
+     * @param objectPath
+     * @param bucketName Name of bucket
+     * @returns S3ObjectMetaInformation Successful Response
+     * @throws ApiError
+     */
+    public static objectGetBucketObject(
+        objectPath: string,
+        bucketName: string,
+    ): CancelablePromise<S3ObjectMetaInformation> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/buckets/{bucket_name}/objects/{object_path}',
+            path: {
+                'object_path': objectPath,
+                'bucket_name': bucketName,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+}
diff --git a/src/client/services/KeyService.ts b/src/client/services/KeyService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f6957867d3e7369fecd3a3dbd1463849a3159a3
--- /dev/null
+++ b/src/client/services/KeyService.ts
@@ -0,0 +1,122 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { S3Key } from '../models/S3Key';
+
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+
+export class KeyService {
+
+    /**
+     * Get the S3 Access keys from a user
+     * Get all the S3 Access keys for a specific user.
+     *
+     * @param username Username of a user
+     * @returns S3Key Successful Response
+     * @throws ApiError
+     */
+    public static keyGetUserKeys(
+        username: string,
+    ): CancelablePromise<Array<S3Key>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/users/{username}/keys',
+            path: {
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Create a Access key for a user
+     * Create a S3 Access key for a specific user.
+     *
+     * @param username Username of a user
+     * @returns S3Key Successful Response
+     * @throws ApiError
+     */
+    public static keyCreateUserKey(
+        username: string,
+    ): CancelablePromise<S3Key> {
+        return __request(OpenAPI, {
+            method: 'POST',
+            url: '/users/{username}/keys',
+            path: {
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get a specific S3 Access key from a user
+     * Get a specific S3 Access Key for a specific user.
+     *
+     * @param accessId ID of the S3 access key
+     * @param username Username of a user
+     * @returns S3Key Successful Response
+     * @throws ApiError
+     */
+    public static keyGetUserKey(
+        accessId: string,
+        username: string,
+    ): CancelablePromise<S3Key> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/users/{username}/keys/{access_id}',
+            path: {
+                'access_id': accessId,
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Delete a specific S3 Access key from a user
+     * Delete a specific S3 Access key for a specific user.
+     *
+     * @param accessId ID of the S3 access key
+     * @param username Username of a user
+     * @returns void
+     * @throws ApiError
+     */
+    public static keyDeleteUserKey(
+        accessId: string,
+        username: string,
+    ): CancelablePromise<void> {
+        return __request(OpenAPI, {
+            method: 'DELETE',
+            url: '/users/{username}/keys/{access_id}',
+            path: {
+                'access_id': accessId,
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+}
diff --git a/src/client/services/MiscellaneousService.ts b/src/client/services/MiscellaneousService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b8b0fc0229ea08bc8e78cf9f78ee2dd06ad1051a
--- /dev/null
+++ b/src/client/services/MiscellaneousService.ts
@@ -0,0 +1,27 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+
+export class MiscellaneousService {
+
+    /**
+     * Health Check
+     * Check the health of the service.
+     *
+     * @returns any Service Health is OK
+     * @throws ApiError
+     */
+    public static miscellaneousHealthCheck(): CancelablePromise<any> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/health',
+            errors: {
+                500: `Service Health is not OK`,
+            },
+        });
+    }
+
+}
diff --git a/src/client/services/ObjectService.ts b/src/client/services/ObjectService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..84598b91af3530fd612d76dca2a9233150711b6b
--- /dev/null
+++ b/src/client/services/ObjectService.ts
@@ -0,0 +1,67 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { S3ObjectMetaInformation } from '../models/S3ObjectMetaInformation';
+
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+
+export class ObjectService {
+
+    /**
+     * Get the metadata of the objects in the bucket
+     * Get the metadata of the objects in the bucket.
+     *
+     * @param bucketName Name of bucket
+     * @returns S3ObjectMetaInformation Successful Response
+     * @throws ApiError
+     */
+    public static objectGetBucketObjects(
+        bucketName: string,
+    ): CancelablePromise<Array<S3ObjectMetaInformation>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/buckets/{bucket_name}/objects',
+            path: {
+                'bucket_name': bucketName,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get the metadata about a specific object
+     * Get the metadata of a specific object in a bucket.
+     *
+     * @param objectPath
+     * @param bucketName Name of bucket
+     * @returns S3ObjectMetaInformation Successful Response
+     * @throws ApiError
+     */
+    public static objectGetBucketObject(
+        objectPath: string,
+        bucketName: string,
+    ): CancelablePromise<S3ObjectMetaInformation> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/buckets/{bucket_name}/objects/{object_path}',
+            path: {
+                'object_path': objectPath,
+                'bucket_name': bucketName,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+}
diff --git a/src/client/services/UserService.ts b/src/client/services/UserService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fd37cb612103acb573849874d4fa6e620372f0d0
--- /dev/null
+++ b/src/client/services/UserService.ts
@@ -0,0 +1,168 @@
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { S3Key } from '../models/S3Key';
+import type { User } from '../models/User';
+
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+
+export class UserService {
+
+    /**
+     * Get the logged in user
+     * Return the user associated with the used JWT.
+     *
+     * @returns User Successful Response
+     * @throws ApiError
+     */
+    public static userGetLoggedInUser(): CancelablePromise<User> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/users/me',
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+            },
+        });
+    }
+
+    /**
+     * Get a user by its username
+     * Return the user with the specific username. A user can only view himself.
+     *
+     * @param username Username of a user
+     * @returns User Successful Response
+     * @throws ApiError
+     */
+    public static userGetUser(
+        username: string,
+    ): CancelablePromise<User> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/users/{username}',
+            path: {
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get the S3 Access keys from a user
+     * Get all the S3 Access keys for a specific user.
+     *
+     * @param username Username of a user
+     * @returns S3Key Successful Response
+     * @throws ApiError
+     */
+    public static keyGetUserKeys(
+        username: string,
+    ): CancelablePromise<Array<S3Key>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/users/{username}/keys',
+            path: {
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Create a Access key for a user
+     * Create a S3 Access key for a specific user.
+     *
+     * @param username Username of a user
+     * @returns S3Key Successful Response
+     * @throws ApiError
+     */
+    public static keyCreateUserKey(
+        username: string,
+    ): CancelablePromise<S3Key> {
+        return __request(OpenAPI, {
+            method: 'POST',
+            url: '/users/{username}/keys',
+            path: {
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Get a specific S3 Access key from a user
+     * Get a specific S3 Access Key for a specific user.
+     *
+     * @param accessId ID of the S3 access key
+     * @param username Username of a user
+     * @returns S3Key Successful Response
+     * @throws ApiError
+     */
+    public static keyGetUserKey(
+        accessId: string,
+        username: string,
+    ): CancelablePromise<S3Key> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/users/{username}/keys/{access_id}',
+            path: {
+                'access_id': accessId,
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+    /**
+     * Delete a specific S3 Access key from a user
+     * Delete a specific S3 Access key for a specific user.
+     *
+     * @param accessId ID of the S3 access key
+     * @param username Username of a user
+     * @returns void
+     * @throws ApiError
+     */
+    public static keyDeleteUserKey(
+        accessId: string,
+        username: string,
+    ): CancelablePromise<void> {
+        return __request(OpenAPI, {
+            method: 'DELETE',
+            url: '/users/{username}/keys/{access_id}',
+            path: {
+                'access_id': accessId,
+                'username': username,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                403: `Not authenticated`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+
+}
diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue
index 040a4f677b61dda6b1cc12add772a07f2d3ddc69..edf18f4400735c1f171ae6eab84b1bdf42cec32b 100644
--- a/src/components/NavbarTop.vue
+++ b/src/components/NavbarTop.vue
@@ -1,11 +1,41 @@
 <script setup lang="ts">
 import BootstrapIcon from "./BootstrapIcon.vue";
+import { reactive, onBeforeUnmount, onMounted } from "vue";
+import { MiscellaneousService } from "@/client";
+
+const api_connection = reactive({ connected: false, timer: null });
+let timer: ReturnType<typeof setInterval> | undefined = undefined;
+function checkApiHealth() {
+  MiscellaneousService.miscellaneousHealthCheck()
+    .then(() => {
+      api_connection.connected = true;
+    })
+    .catch(() => {
+      api_connection.connected = false;
+    });
+}
+
+onMounted(() => {
+  checkApiHealth();
+  timer = setInterval(checkApiHealth, 10000);
+});
+onBeforeUnmount(() => {
+  clearInterval(timer);
+});
 </script>
 
 <template>
   <nav class="navbar fixed-top navbar-expand-lg bg-dark">
     <div class="container-fluid">
-      <router-link class="navbar-brand ms-3" to="/">S3 Proxy</router-link>
+      <router-link class="navbar-brand ms-3 text-light" to="/"
+        >S3 Proxy</router-link
+      >
+      <span
+        v-if="!api_connection.connected"
+        class="navbar-text text-light text-bg-danger p-1"
+      >
+        Backend not reachable
+      </span>
       <div class="dropdown d-flex me-3">
         <a
           href="#"
diff --git a/tsconfig.json b/tsconfig.json
index 8d2359999ec907a1ff44010999ff814fde4897a5..0f216f1416aff7a546a0218b93976f2282c8460f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,6 +2,8 @@
   "extends": "@vue/tsconfig/tsconfig.web.json",
   "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
   "compilerOptions": {
+    "lib": ["...", "dom"],
+    "allowSyntheticDefaultImports": true,
     "baseUrl": ".",
     "paths": {
       "@/*": ["./src/*"]